36

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.

kenba
  • 4,303
  • 1
  • 23
  • 40
  • 7
    In C++ you'd probably make a `Year` class with validation at the constructor and/or assignment level. Like `auto year = new Year(2002)` then `year.old_enough()`, or for a more strict implementation, `throw` when an invalid value is supplied. – tadman Jun 26 '20 at 07:47
  • 2
    if you really only need the same type but as a different type, it requires quite some boilerplate unfortunately. Fortunately someone wrote it already, you can use `BOOST_STRONG_TYPEDEF`, but in general I agree with tad, a `Year` is not an `int` and deserves its own type – 463035818_is_not_an_ai Jun 26 '20 at 07:48
  • My [related question](https://stackoverflow.com/questions/52045697/class-vs-enum-class-as-an-index-type) (unanswered). – Evg Jun 26 '20 at 07:50
  • @tadman since in C++ the only difference between class and struct is the default visibility of members, that's basically their third option (I concur BTW). Although I think option #2 might be useful if you need strong size guarantees (with a struct you might get a vtable pointer?) – Masklinn Jun 26 '20 at 07:51
  • 1
    Didn't Bjarne intended a class to be a custom type? A new type could be implemented in the class itself or later as a child of the parent class. – Ingo Mi Jun 26 '20 at 07:54
  • @Masklinn Although it's often subjective, it makes more sense for `struct` to be "dumb data" and `class` to be "object encapsulated". In this case it needs some smarts, so `class` seems to be the way to go. – tadman Jun 26 '20 at 09:23
  • 3
    Does this answer your question? [Strongly typed using and typedef](https://stackoverflow.com/questions/34287842/strongly-typed-using-and-typedef) – underscore_d Jun 26 '20 at 09:55
  • 1
    @underscore_d thank you it's very similar but, unfortunately it does not answer my question. – kenba Jun 26 '20 at 10:13
  • 3
    @tadman You mean `auto year = Year(2002);` – user253751 Jun 26 '20 at 13:46
  • @user253751 Correct. That's what flipping between JavaScript and C++ will do for you. – tadman Jun 26 '20 at 15:17
  • 1
    I think your definition of `newtype` is missing a crucial element: `newtype`s are used *only* for typing and otherwise fully erased. In other words, if you replace `Days` and `Years` with `int` in your code examples, there *must be no difference in the behavior or in the generated code*. `newtype`s *must not* influence behavior or generated code, only typing. Essentially, the only thing they can do is reject programs (in Haskell). In languages like C++ with type-based compile-time overloading, that is a different story. – Jörg W Mittag Jun 26 '20 at 20:25
  • 1
    @JörgWMittag you raise a good point about `newtype` in `Haskell`. The Rust implementation is also "zero overhead", which I consider to be a key feature of the idiom. – kenba Jun 26 '20 at 21:52
  • 2
    It might be worth noting that for this particular example, the C++ standard library already has `std::chrono::years` and `std::chrono::days` which are essentially this type of wrapper already. (Though in this case, there also also implicit conversions possible from e.g. `std::chrono::days` to `std::chrono::seconds` which behind the scenes will multiply the value by the correct conversion factor.) Boost also has a units library which handles wrapping double values in SI (or imperial units) and doing conversions behind the scenes. – Daniel Schepler Jun 27 '20 at 01:15
  • @DanielSchepler that is an excellent point! Unfortunately, I used the [Rust newtype](https://doc.rust-lang.org/stable/rust-by-example/generics/new_types.html) example, instead of something like Feet, Metres, Seconds, MetersPerSecond, etc., where the `boost units` library would be the "standard method". You should make your comment an answer. – kenba Jun 27 '20 at 06:42
  • @kenba:It is not clear how the "newtype idiom" is different from strong typedefs. The latter is a mechanism, while the former is just a specific use for the mechanism. That is, how would a strong typedef be different from finding a way to emulate Rust's "newtype" concept in C++'s type system? – Nicol Bolas Jul 03 '20 at 22:56

3 Answers3

15

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.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 3
    You can go more lightweight and avoid the `enum` taking advantage of "inline" struct declarations: `using YearNumber = Number;` – Matthieu M. Jun 26 '20 at 09:40
  • 1
    @MatthieuM. That is super neat, I did not know that you declare structs as a type template argument in the template argument list. Thanks, I will update the answer shortly. – dfrib Jun 26 '20 at 09:41
  • @MatthieuM. I guess one limitation with that approach is in case you want to e.g. specialize some non-template member function of the `Number` class template based on its tag, and/or delegate some non-template member functions to concrete custom (private) member functions via tag dispatch. It's easily adapted though, were that use case to come up. – dfrib Jun 26 '20 at 09:58
  • @MatthieuM. It works! That blows my mind. IIRC anonymous structs aren't allowed as template type arguments. But it seems that named-but-entirely-local ones are? O.o I'd be interested in the Standardese supporting that, but I guess it's too long/fragmented ;) Anyway, even if declared out-of-line, tag structs are always preferable here, I think, rather than only allowing a set of enumerators defined ahead of time. That way, anyone can make their own new tag to use the template. (not an issue in purely internal code, but why use a value where a type will do, is my logic!) – underscore_d Jun 26 '20 at 10:01
  • 1
    @underscore_d Afaik anon. structs are, by requirement, always _defined_ at their declarations. IIRC [WG21/DR 62](http://open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#62) touched upon this (_"The following types shall not be used as a template-argument for a template type-parameter: ... an unnamed class or enum. type that has no name for linkage purposes"_). I can't find, from [\[temp.arg.type\]](https://timsong-cpp.github.io/cppwp/n4659/temp.arg.type), however, that a _class-specifier_ is a valid _template-argument_, but this apparently works (with all compilers I have tried). Thoughts? – dfrib Jun 26 '20 at 10:12
  • @dfri I would prefer your answer if it were not `int` specific, i.e. if the template also took the underlying type as a parameter. – kenba Jun 26 '20 at 10:25
  • @kenba updated to add a parameterizable `ValueType` type template parameter (however with not SFINAE restrictions placed on this type; out of scope). – dfrib Jun 26 '20 at 10:30
  • 1
    @underscore_d As I wasn't able to find the standardese covering (incomplete) class declarations in a _template-argument_ for a non-type template parameter, [I posted a question about it](https://stackoverflow.com/questions/62593093), for the more experienced language lawyers to have a go at. – dfrib Jun 26 '20 at 10:54
  • Another way to provide the tag inline would be CRTP, e.g. `struct Years : newtype { };` – Daniel Schepler Jun 27 '20 at 01:31
4

I have used boost strong typedef in the past. The documentation on it seems quite sparse, but fwiw, it seems to be used by facebook, and LLVM seems to have a similar thing called LLVM_YAML_STRONG_TYPEDEF, indicating that it may have had some real-world-exposure.

phimuemue
  • 34,669
  • 9
  • 84
  • 115
2

If you have , 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, 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 , 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

darune
  • 10,480
  • 2
  • 24
  • 62