Since learning Rust, I've become a fan of the newtype idiom which I gather Rust borrowed from Haskell.
A newtype is a distinct type based on a standard type which ensures that function parameters are of the correct type.
For example, the old_enough
function below must be passed an age in Years. It will not compile with an age in Days or as a plain i64.
struct Days(i64);
struct Years(i64);
fn old_enough(age: &Years) -> bool {
age.0 >= 18
}
This is different from a typedef
or using
declaration in C++ which simply renames the type.
For example the old_enough
function below would accept an int
, an age in Days
, or anything else that converts to an int
:
typedef int Days;
using Years = int;
bool old_enough(Years age) {
return age >= 18;
}
As the example above just uses integers, this post on Reddit suggests using enum classes, e.g.:
enum class Days : int {};
enum class Years : int {};
bool old_enough(Years age) {
return static_cast<int>(age) >= 18;
}
Or it could simply use structures, like Rust e.g.:
struct Days final {int value;};
struct Years final {int value;};
bool old_enough(Years age) {
return age.value >= 18;
}
What is the best way to implement the newtype
idiom in C++
?
Is there a standard method?
EDIT the question Strongly typed using and typedef is similar. However, it does not consider the newtype
idiom.
If you have boost, BOOST_STRONG_TYPEDEF does exactly what you want as already seen in this answer.
There is nothing in the c++ language (yet) that can do it directly as you want. But then again, detailed needs could be different, eg. someone might say it's ok to do an implicit construction where as another might say it has to be explicit. Due to that and other combinations1 it is difficult to provide one mechanism that will satisfy everyone and we already have normal type alias (ie. using
, which ofc. is different from a strong typedef).
That being said, c++ gives you enough tools that you can build this generic tool yourself and it is not completely difficult to do if you have some experience with templates, etc..
In the end it depends on what newtype issues you actually have, eg. do you just need a handfull or are you going to make these in bulks. For something ordinary like Years and Days you could just use bare structs:
struct Days {int value;};
struct Years {int value;};
However, if you must avoid a situation like this:
bool isold(Years y);
...
isold({5});
You then must make a constructor and make it explicit, ie.:
struct Years {
explicit Years(int i);
...
1 another combination could for example be if the new type should be allowed to convert to the underlying type, could be usefull for something like int
, or it could be dangerous depending on context