RFC: Strong typedef for LLVM

Lately I've been using some utilities to increase the number of logic
errors caught at compile time. I thought they might be useful to the
LLVM project. I'd appreciate feedback on the below proposal. Would the
community find these useful?

                          -David

RFC: Strong typedef utilities for LLVM

+1, I like this a lot.

Recently, I started to use two element `enum class`es to force people (mostly me)
to spell out what true/false actually means in a certain context. The solution proposed
here comes with more boilerplate but once available it is nicer (I think) and for sure
more generic.

I agree, I think for bools, an enum class is the simplest language provided way to get most of the benefits that a strong typedef (or value wrapper class) would provide. I would prefer to keep using them for these cases, especially for widely used user facing APIs. The less publicly visible the function is (private method, file local static, etc), the more inclined I am to just keep doing what we’re doing for expediency and brevity.

For integer values that are easy to confuse (physical register numbers, byte quantities, bit quantities, alignments, etc), I agree, we should be leveraging the type system more than we are. Adding a strong typedef class to make it easier to make these types with less boilerplate would be great. See existing prior art for this kind of stuff in clang::CharUnits.

I suppose I don’t love the name “strong typedef”, since it kind of implies a value judgement that C typedefs are weak, and therefore worse. They aren’t worse, they just aren’t well suited to this use case and have other strengths and weaknesses. I see that the C++ paper proposes calling these “opaque typedefs”. I don’t know if that’s the best name, but I prefer that shade of paint. :slight_smile:

Reid Kleckner <rnk@google.com> writes:

+1, I like this a lot.

Recently, I started to use two element `enum class`es to force people (mostly me)
to spell out what true/false actually means in a certain context. The solution proposed
here comes with more boilerplate but once available it is nicer (I think) and for sure
more generic.

I agree, I think for bools, an enum class is the simplest language
provided way to get most of the benefits that a strong typedef (or
value wrapper class) would provide. I would prefer to keep using them
for these cases, especially for widely used user facing APIs. The less
publicly visible the function is (private method, file local static,
etc), the more inclined I am to just keep doing what we're doing for
expediency and brevity.

This all sounds fine. My implementation of NamedBoolean allows this
kind of thing:

  struct AFlag : public NamedBoolean<AFlag> {
    using NamedBoolean::NamedBoolean;
  };

  struct AnotherFlag : public NamedBoolean<AnotherFlag> {
    using NamedBoolean::NamedBoolean;
  };

  AFlag flag1;
  AnotherFlag flag2;

  if (flag1 || flag2) { // No explicit casts needed
    ...
  };

I don't think that's quite as convenient with `enum class` but maybe
that's a minor point. Both spellings might be useful in different
contexts.

For integer values that are easy to confuse (physical register
numbers, byte quantities, bit quantities, alignments, etc), I agree,
we should be leveraging the type system more than we are. Adding a
strong typedef class to make it easier to make these types with less
boilerplate would be great. See existing prior art for this kind of
stuff in clang::CharUnits.

Thanks for the pointer! It looks like CharUnits has more functionality
than the proposed classes. I intend for these classes to be as simple
as possible to handle common cases. I wouldn't want to see them grow to
implement all of the interfaces required by CharUnits.

I suppose I don't love the name "strong typedef", since it kind of
implies a value judgement that C typedefs are weak, and therefore
worse. They aren't worse, they just aren't well suited to this use
case and have other strengths and weaknesses. I see that the C++ paper
proposes calling these "opaque typedefs". I don't know if that's the
best name, but I prefer that shade of paint. :slight_smile:

OTOH, naming it OpaqueTypedef might give users the mistaken impression
that all of the qualities guaranteed by that proposal are provided by
these utilities. That is almost certainly not the case in general
(equality of sizes, for example).

I also think "opaque" is sort of a strange name. The types aren't
opaque. You can do ordinary operations on them (overloaded operators,
etc.). I tend to think of an "opaque" type as something that I can't
really do anything with except pass to/from some black-box interfaces.

That said, I'm not married to the name. If there is some sort of
consensus around a different name that's great! I don't love the Named*
names but couldn't think of anything better.

                        -David

I’m generally a fan of using the type system to encapsulate this kind of thing. A few points:

  • Why no move assignment operator?
  • swap has the wrong noexcept specification.
  • In my experience, most of these types end up being equality comparable and hashable. I understand that equality comparison is separate concern which can be provided by mixins, but the more boilerplate that is required to use it, the less likely people are to adopt it. This could, of course, be constrained on whether or not the BaseType supports these operations.
  • Similar argument about using CRTP, as it requires more boilerplate (the using StrongTypedef::StrongTypedef; statement to bring in the converting constructor).
  • Not a fan of the name StrongTypedef (or OpaqueTypedef) either. If the C++ standard ever acquires related functionality, there will end up being a conceptual mismatch.

Nevin Liber via llvm-dev <llvm-dev@lists.llvm.org> writes:

I'm generally a fan of using the type system to encapsulate this kind of thing. A few points:

* Why no move assignment operator?

It's a sketch. :slight_smile: The actual code will have a lot more.

* swap has the wrong noexcept specification.

Noted, thanks.

* In my experience, most of these types end up being equality
comparable and hashable. I understand that equality comparison is
separate concern which can be provided by mixins, but the more
boilerplate that is required to use it, the less likely people are to
adopt it. This could, of course, be constrained on whether or not the
BaseType supports these operations.

Let's talk about this in the design review when I post the patch. I'll
definitely add you as a reviewer.

* Similar argument about using CRTP, as it requires more boilerplate
(the using StrongTypedef::StrongTypedef; statement to bring in the
converting constructor).

Ditto above, though without CRTP operator overloading becomes less
convenient.

* Not a fan of the name StrongTypedef (or OpaqueTypedef) either. If
the C++ standard ever acquires related functionality, there will end
up being a conceptual mismatch.

What about "ExplicitWrapper?" Let's talk more when the patch is posted.

Thanks for your helpful feedback!

                       -David

The syntax of using bool’s is fine, but the semantics are hard to get right, so how about SemanticTypedef?

For anyone interested, I've posted a patch as D66148.

https://reviews.llvm.org/D66148

                     -David

"Doerfert, Johannes" <jdoerfert@anl.gov> writes: