What is the best way to implement the
newtype
idiom in C++?
Rating the best many times end up in the preferential domain, but you’re already mentioned two alternative approaches yourself: simply custom structs wrapping a value of a common type (say int
), or using enum
classes with an explicitly specified underlying type for strongly type near-identical types.
If you’re mainly after strongly typed type aliases of a common type, say
struct Number { int value; }
or, a common type with a parameterizable underlying type
template<typename ValueType = int>
struct Number { ValueType value; }
then another common approach (which also facilitates re-using functionality between strongly type-distinct but related types) is making(/expanding) the Number
class (template) a class template parameterized over type template tag parameter, such that specializations over the tag types results in strong typing. As pointed out by @Matthieu M., we may declare a struct as part of the template argument list to a given specialization, allowing a lightweight tag declaration and alias tagging in a single alias declaration:
template<typename Tag, typename ValueType = int>
struct Number {
ValueType value;
// ... common number functionality.
};
using YearNumber = Number<struct NumberTag>;
using DayNumber = Number<struct DayTag>;
void takeYears(const YearNumber&) {}
void takeDays(const DayNumber&) {}
int main() {
YearNumber y{2020};
DayNumber d{5};
takeYears(y);
//takeDays(y); // error: candidate function not viable
takeDays(d);
//takeYears(d); // error: candidate function not viable
return 0;
}
Note that in case you would like to specialize non-member functions of the Number
class template for specific tags (or e.g. use tag dispatch for a similar purpose), you would need to declare the type tags outside of the alias declaration.