We already have strong typedefs in C++17

3,071 views
Skip to first unread message

the.ultim...@gmail.com

unread,
Dec 22, 2017, 6:06:26 PM12/22/17
to ISO C++ Standard - Future Proposals
realized two days ago that there is no need to invent new syntax to bring strong typedefs into C++. We already have them.

Indeed, we already have a construct that take a type as a parameter, and creates a new type, gives it a new name, uses the passed argument as its implementation, and this new type is not implicitly convertible to and from the argument.

It's enum class.

However I look at it now, I can't consider it otherwise. enum class is and means strong typedef. And now, instead of thinking "enum class for creating strong typedefs is no good. enum class is for creating strong enums. We have to invent something else.", I think "enum class is strong typedef". And C++17 strong typedef sucks in two ways :
  • You can only apply strong typedef to integral types.
  • You can only add free functions as additional behaviour. (well, almost)

These problems are orthogonal. Fix both, and you get full-fledged strong typedefs.


#1


The first problem can be fixed simply by allowing any type as the underlying type of an enum. If the type is not literal, you can still use it as underlying type. The only thing that requires the type to be literal is the declaration of enumerators.


#2


The second problem finds its solution in paper P0707Rx (metaclasses), when Herb Sutter thanks Dennis Ritchie twice for his insight of using the same syntax for structs ans unions. Let's go further and use the same syntax for struct, union and enums. Here is the recipe: (I may be wrong on any step, it looks too simple)

  1. Add the production rules of enum-key to class-key
  2. Verify that enum-head now defines a proper sublanguage of class-head
  3. Replace the production rules of class-specifier by the following :
    class-head { enumerator-list ; member-specification }
    class-head { ;(opt) member-specification(opt) }
    class-head { enumerator-list ;(opt) }
    class-head { enumerator-list , }
  4. Verify that enum-specifier now defines a proper sublanguage of class-specifier
  5. Have class-specifier specifiy enumeration types in addition of what it already does.
  6. Get rid of enum-specifier and subsequently orphan production rules altogether.
  7. Forbid what was forbidden through syntax in C++17 by explicitly prosed restrictions. Merge chapter 7 into chapter 6.
  8. Get Committee approval for a gratuitous syntax change that brings no functionality, edits boatload of text to the Standard, yet breaks no code.
  9. Take time to think about what to allow for the enumeration types that could have members and be templated if we let them, and for class types that could declare enumerators if we let them.
  10. Lift restrictions as deemed wise.
Annoying details comfortably ignored, I think it's the way to go. What do you think ?

Nicol Bolas

unread,
Dec 22, 2017, 6:42:06 PM12/22/17
to ISO C++ Standard - Future Proposals


On Friday, December 22, 2017 at 6:06:26 PM UTC-5, Laurent LA RIZZA wrote:
realized two days ago that there is no need to invent new syntax to bring strong typedefs into C++. We already have them.

Indeed, we already have a construct that take a type as a parameter, and creates a new type, gives it a new name, uses the passed argument as its implementation, and this new type is not implicitly convertible to and from the argument.

It's enum class.

However I look at it now, I can't consider it otherwise. enum class is and means strong typedef. And now, instead of thinking "enum class for creating strong typedefs is no good. enum class is for creating strong enums. We have to invent something else.", I think "enum class is strong typedef". And C++17 strong typedef sucks in two ways :
  • You can only apply strong typedef to integral types.
  • You can only add free functions as additional behaviour. (well, almost)

These problems are orthogonal. Fix both, and you get full-fledged strong typedefs.


No.

It was a mistake for the committee to use `enum class` to create strong typedefs. It would only compound this error to continue this belief.

The problem with strong typedefs has never been the question of "what syntax do I use?" It's always been a matter of what behavior you get in the myriad of circumstances where you want strong typedef. That is, the question of how strong of an alias do you want.

`enum class` works as a strong typedef for integral types because there's pretty much only one level of "strength" as far as integers are concerned. User-defined types have many possible levels of "strength", and different people have different needs.

Laurent LA RIZZA

unread,
Dec 23, 2017, 4:55:15 AM12/23/17
to ISO C++ Standard - Future Proposals


Le samedi 23 décembre 2017 00:42:06 UTC+1, Nicol Bolas a écrit :
<snip/>
It was a mistake for the committee to use `enum class` to create strong typedefs. It would only compound this error to continue this belief.


The committee did not use enum class to create strong typedefs. They used enum class to create scoped enums from unscoped enums.

The only adopted proposal I know of that links enum class to strong typedef is P0138R2. The only justification of the paper is indeed "hey, we're using enum class to create strong typedefs of integral types. Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs.

The problem with strong typedefs has never been the question of "what syntax do I use?" It's always been a matter of what behavior you get in the myriad of circumstances where you want strong typedef. That is, the question of how strong of an alias do you want.

`enum class` works as a strong typedef for integral types because there's pretty much only one level of "strength" as far as integers are concerned. User-defined types have many possible levels of "strength", and different people have different needs.

Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?
 

Nicol Bolas

unread,
Dec 23, 2017, 10:37:41 AM12/23/17
to ISO C++ Standard - Future Proposals


On Saturday, December 23, 2017 at 4:55:15 AM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 00:42:06 UTC+1, Nicol Bolas a écrit :
<snip/>
It was a mistake for the committee to use `enum class` to create strong typedefs. It would only compound this error to continue this belief.


The committee did not use enum class to create strong typedefs. They used enum class to create scoped enums from unscoped enums. 

The only adopted proposal I know of that links enum class to strong typedef is P0138R2. The only justification of the paper is indeed "hey, we're using enum class to create strong typedefs of integral types. Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

Yes, that was exactly what I was referring to, and yes, it was a mistake.

This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs.

Compounding a mistake with another mistake is absurd.


The problem with strong typedefs has never been the question of "what syntax do I use?" It's always been a matter of what behavior you get in the myriad of circumstances where you want strong typedef. That is, the question of how strong of an alias do you want.

`enum class` works as a strong typedef for integral types because there's pretty much only one level of "strength" as far as integers are concerned. User-defined types have many possible levels of "strength", and different people have different needs.

Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?

Go read every strong typedef proposal that has ever been proposed. They do a good job of covering the myriad of possible ways people want to deal with such things.

However, as a simple example, let's say you have a class A, and you make a strong typedef of it called B. Can B do everything that A could do? For example, if A was Swappable, that means A probably has a non-member `swap` function. Does B automatically get one? Should it? If A was addable to integers via non-member `operator+`, should B be addable too?

And if you can't automatically get the non-member interfaces of A, then what good is B?

Richard Smith

unread,
Dec 23, 2017, 11:52:12 AM12/23/17
to std-pr...@isocpp.org
On 23 December 2017 at 09:55, Laurent LA RIZZA <the.ultim...@gmail.com> wrote:
Le samedi 23 décembre 2017 00:42:06 UTC+1, Nicol Bolas a écrit :
<snip/>
It was a mistake for the committee to use `enum class` to create strong typedefs. It would only compound this error to continue this belief.


The committee did not use enum class to create strong typedefs. They used enum class to create scoped enums from unscoped enums.

The only adopted proposal I know of that links enum class to strong typedef is P0138R2. The only justification of the paper is indeed "hey, we're using enum class to create strong typedefs of integral types. Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs. 

We don't have good ones, though; look at the library portion of the <=> design (particularly, p0515r3 section 3) for an example of what we're missing. "enum class" was considered as a way of making std::strong_ordering and friends be strong typedefs for int, but they don't work. In that particular case the problem is that we wish to permit certain kinds of conversion between std::strong_ordering and its related classes, but that is not possible when using enum class to define a "strong typedef".
 
The problem with strong typedefs has never been the question of "what syntax do I use?" It's always been a matter of what behavior you get in the myriad of circumstances where you want strong typedef. That is, the question of how strong of an alias do you want.

`enum class` works as a strong typedef for integral types because there's pretty much only one level of "strength" as far as integers are concerned. User-defined types have many possible levels of "strength", and different people have different needs.

Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?
 

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f372e674-f341-4fbb-a1d3-77548284e13e%40isocpp.org.

Laurent LA RIZZA

unread,
Dec 23, 2017, 11:55:42 AM12/23/17
to ISO C++ Standard - Future Proposals


Le samedi 23 décembre 2017 16:37:41 UTC+1, Nicol Bolas a écrit :
 
Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

Yes, that was exactly what I was referring to, and yes, it was a mistake.

I still don't know, that does not change the fact that even before that "mistake", people were using enum class to create strong typedefs of integral types. We have strong typedefs in C++17.
 
This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs.

Compounding a mistake with another mistake is absurd.

Same answer. Adoption of P0138R2 is irrelevant to my-mine-and-only-of-me statement that we have strong typedefs in C++17. It being a mistake is your opinion, not a fact.


Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?

Go read every strong typedef proposal that has ever been proposed. They do a good job of covering the myriad of possible ways people want to deal with such things.

I have done (a long time ago), and will do again. I thank you for expressing your opinions on my ideas.

However, as a simple example, let's say you have a class A, and you make a strong typedef of it called B.

OK, so :

enum class B : A {};

Can B do everything that A could do?

No. It can be explicitly convertible to and from A (with and without variable numbers of ampersands, but I haven't thought it through) If A is literal, you can define named constants of type B. You can add also add member non-variables.
 
For example, if A was Swappable, that means A probably has a non-member `swap` function.

enum class B : A {
public :
  friend void swap(B&, B&) = default;
};

Does B automatically get one?

No.

Should it?

No.

If A was addable to integers via non-member `operator+`, should B be addable too?

No, but :

enum class B : A {
public :
  friend B operator+(const B&, int) = default;
};

And if you can't automatically get the non-member interfaces of A, then what good is B?
 
B is a distinct type from A, explicitly convertible to and from it. B is stored the same way as A. It is as copyable, movable, assignable and destructible as A, except the signatures get B in lieu of A.

You can't even have them non-automatically in C++17, since you can't define B if A is not an integral type.

A question for you : if using enum class for creating strong aliases of integral types in C++17 is a mistake, how do you create strong aliases of integral types in C++17?

Nicol Bolas

unread,
Dec 23, 2017, 12:14:40 PM12/23/17
to ISO C++ Standard - Future Proposals
On Saturday, December 23, 2017 at 11:55:42 AM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 16:37:41 UTC+1, Nicol Bolas a écrit :
 
Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

Yes, that was exactly what I was referring to, and yes, it was a mistake.

I still don't know, that does not change the fact that even before that "mistake", people were using enum class to create strong typedefs of integral types.

People also used template instantiation to do compile-time computations. C++ programmers are willing to use any feature for any purpose, so long as it works. That doesn't mean we should enshrine it as the right idea. If people want to do compile-time computations, we should give them functions that run at compile-time, not make them jump through hoops of type instantiation and so forth.

You're basically saying, "we have template metaprogramming; why not add more to that, instead of bothering with this whole `constexpr` function thing?" Or "we have SFINAE; why not add more to that, instead of bothering with this whole `requires` thing?"

We have strong typedefs in C++17.

No, we have hacks in C++17.

This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs.

Compounding a mistake with another mistake is absurd.

Same answer. Adoption of P0138R2 is irrelevant to my-mine-and-only-of-me statement that we have strong typedefs in C++17. It being a mistake is your opinion, not a fact.

If you weren't interested in the opinions of others, I don't know why you felt the need to post here.



Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?

Go read every strong typedef proposal that has ever been proposed. They do a good job of covering the myriad of possible ways people want to deal with such things.

I have done (a long time ago), and will do again. I thank you for expressing your opinions on my ideas.

However, as a simple example, let's say you have a class A, and you make a strong typedef of it called B.

OK, so :

enum class B : A {};

Can B do everything that A could do?

No. It can be explicitly convertible to and from A (with and without variable numbers of ampersands, but I haven't thought it through) If A is literal, you can define named constants of type B. You can add also add member non-variables.
 
For example, if A was Swappable, that means A probably has a non-member `swap` function.

enum class B : A {
public :
  friend void swap(B&, B&) = default;
};

Does B automatically get one?

No.

Should it?

No.

If A was addable to integers via non-member `operator+`, should B be addable too?

No, but :

enum class B : A {
public :
  friend B operator+(const B&, int) = default;
};

And if you can't automatically get the non-member interfaces of A, then what good is B?
 
B is a distinct type from A, explicitly convertible to and from it. B is stored the same way as A. It is as copyable, movable, assignable and destructible as A, except the signatures get B in lieu of A.

But it's not actually an alias of `A`, since it cannot do what `A` does.

My point is that C++ has no mechanism to identify free functions that are a fundamental part of a class's interface from free functions which are not. And without that, making proper strong aliases becomes a maintenance burden. If someone adds a new free function interface to A, you have to add a `friend` declaration to every strong alias of it in order to pick up that behavior.

My other point is that you're spending way too much time on the syntax (insisting on overloading "enum class" for things that aren't enumerators) and not enough on the actually useful parts of the feature. For example, this is the first thing you've mentioned about this `= default` syntax for inheriting functionality from the class being aliased. That's a really important part of the proposal, and yet you didn't even mention that in your initial post, instead spending time trying to justify "enum class" as the correct induction syntax.

Why do you care so much that it gets called "enum class" instead of, like, anything else?

You can't even have them non-automatically in C++17, since you can't define B if A is not an integral type.

A question for you : if using enum class for creating strong aliases of integral types in C++17 is a mistake, how do you create strong aliases of integral types in C++17?

The same way I did in C++14: with an actual class. It's verbose, but at least it isn't stupid looking and confusing.

Laurent LA RIZZA

unread,
Dec 23, 2017, 12:15:16 PM12/23/17
to ISO C++ Standard - Future Proposals


Le samedi 23 décembre 2017 17:52:12 UTC+1, Richard Smith a écrit :
On 23 December 2017 at 09:55, Laurent LA RIZZA <the.ultim...@gmail.com> wrote:


This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs. 

We don't have good ones, though;

That's what I said in my OP. I claim that we have them, but they suck in two specific ways. That's why I proposed to fix them.

look at the library portion of the <=> design (particularly, p0515r3 section 3) for an example of what we're missing. "enum class" was considered as a way of making std::strong_ordering and friends be strong typedefs for int, but they don't work. In that particular case the problem is that we wish to permit certain kinds of conversion between std::strong_ordering and its related classes, but that is not possible when using enum class to define a "strong typedef".

... in C++17. Now, with what I propose, conversions would look like this :

enum class strong_equality : int {
  equal = 0,
  equivalent = equal,
  nonequal = 1,
  nonequivalent = nonequal;
public:
  operator weak_equality(strong_equality se) const {
    return (weak_equality)(int)se;
  }
};

And M. Smith, you might be the appropriate person to tell me whether the #2 rough outline in my OP is feasible (or specific reasons for which it is not). Can you please have a look?

Nicol Bolas

unread,
Dec 23, 2017, 12:27:26 PM12/23/17
to ISO C++ Standard - Future Proposals


On Saturday, December 23, 2017 at 12:15:16 PM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 17:52:12 UTC+1, Richard Smith a écrit :
On 23 December 2017 at 09:55, Laurent LA RIZZA <the.ultim...@gmail.com> wrote:


This is my point. People already create strong typedefs of integral types using enum class. I also do. Get over it. We have strong typedefs. 

We don't have good ones, though;

That's what I said in my OP. I claim that we have them, but they suck in two specific ways. That's why I proposed to fix them.

look at the library portion of the <=> design (particularly, p0515r3 section 3) for an example of what we're missing. "enum class" was considered as a way of making std::strong_ordering and friends be strong typedefs for int, but they don't work. In that particular case the problem is that we wish to permit certain kinds of conversion between std::strong_ordering and its related classes, but that is not possible when using enum class to define a "strong typedef".

... in C++17. Now, with what I propose, conversions would look like this :

enum class strong_equality : int {
  equal = 0,
  equivalent = equal,
  nonequal = 1,
  nonequivalent = nonequal;
public:
  operator weak_equality(strong_equality se) const {
    return (weak_equality)(int)se;
  }
};

I don't see how that's an improvement over what we got in the standard:

class strong_equality
{
  int value;

  strong_equality(int val) : value{val} {}

public:
  constexpr static strong_equality equal = {0};
  constexpr static strong_equality equivalent = equal;
  constexpr static strong_equality nonequal = {1};
  constexpr static strong_equality nonequivalent = nonequal;

  operator weak_equality() const {
    return weak_equality{value};
  }
};

Sure, it's shorter... slightly. But it's a lot more explicit about what it's doing. It does not at any point pretend that it is an enumeration. Which is kind of important; in your case, `is_enum_v<strong_equality>` is true; in my case, it is not.

And M. Smith, you might be the appropriate person to tell me whether the #2 rough outline in my OP is feasible (or specific reasons for which it is not).

It's not. You cannot take an entire primary classification of types and make them something else. Enumerators are a distinct set of types from class types, fundamental types, and so forth. Making them into class types is not a viable process at this point; it'd require too many changes and may not even be backwards compatible.

Laurent LA RIZZA

unread,
Dec 23, 2017, 1:18:55 PM12/23/17
to ISO C++ Standard - Future Proposals


Le samedi 23 décembre 2017 18:27:26 UTC+1, Nicol Bolas a écrit :
in your case, `is_enum_v<strong_equality>` is true; in my case, it is not.


That is entirely true. But I think I invalidated the stated reason why enum class was not used to create a strong typedefs in this paper.


You cannot take an entire primary classification of types and make them something else. Enumerators are a distinct set of types from class types, fundamental types, and so forth.

Yes. At step 9 of my recipe, they still are. I'm just trying to have class/struct/unions parsed uniformly, to make it easier to add missing features on both sides.

Nicol Bolas

unread,
Dec 23, 2017, 2:16:58 PM12/23/17
to ISO C++ Standard - Future Proposals


On Saturday, December 23, 2017 at 1:18:55 PM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 18:27:26 UTC+1, Nicol Bolas a écrit :
in your case, `is_enum_v<strong_equality>` is true; in my case, it is not.


That is entirely true. But I think I invalidated the stated reason why enum class was not used to create a strong typedefs in this paper.

... how? `enum class` cannot do what you did; the standard doesn't allow that.

Now, you can say that, if your proposal is adopted, you could use `enum class` for the results of `operator<=>`. But until your proposal is actually adopted, the reasons that `operator<=>` didn't use them are still quite valid. So you've invalidated nothing.

And even then, your proposal isn't good enough. Why? Because `strong_equality{10}` would still have to be legal code for your `enum class`.

Oh, I'm sure you'll now add some syntax to your "proposal" that explicitly disables such conversion (perhaps an `= delete` constructor or some such). But that's just another reminder that `enum class` is the wrong tool for strong aliases. You're trying to hijack an existing syntax rather than creating a good one.

There is no reason to use `enum class` for inducing a strong alias than any other syntax.

Laurent LA RIZZA

unread,
Dec 23, 2017, 3:33:41 PM12/23/17
to ISO C++ Standard - Future Proposals


Le samedi 23 décembre 2017 20:16:58 UTC+1, Nicol Bolas a écrit :


On Saturday, December 23, 2017 at 1:18:55 PM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 18:27:26 UTC+1, Nicol Bolas a écrit :
in your case, `is_enum_v<strong_equality>` is true; in my case, it is not.


That is entirely true. But I think I invalidated the stated reason why enum class was not used to create a strong typedefs in this paper.

... how? `enum class` cannot do what you did; the standard doesn't allow that.

Now, you can say that, if your proposal is adopted, you could use `enum class` for the results of `operator<=>`. But until your proposal is actually adopted, the reasons that `operator<=>` didn't use them are still quite valid. So you've invalidated nothing.

And even then, your proposal isn't good enough. Why? Because `strong_equality{10}` would still have to be legal code for your `enum class`.

Oh, I'm sure you'll now add some syntax to your "proposal" that explicitly disables such conversion (perhaps an `= delete` constructor or some such).

Correct. Good to read that, besides my consistent failure to convey my ideas clearly, you still can trace through the same path of thought that I followed. It must not be thinking so absurdly after all.

But that's just another reminder that `enum class` is the wrong tool for strong aliases.

No. That's actually a reminder that `enum class` and `enum` need to go from half-baked to well-done, or be deprecated altogether.

You're trying to hijack an existing syntax rather than creating a good one.


No. I'm trying to demonstrate that the syntax for class types and enumeration types can be made the same. (although I am not entirely happy with what I proposed in my OP)

There is no reason to use `enum class` for inducing a strong alias than any other syntax.

Correct also. So let's not do that. Leave strong typedefs aside. Instead, let's do two things :

#1 I'd like to be able to enumerate named constants (§10.2) of any literal type, not just integral types. I could declare inline const constexpr member variables in a class type, but I don't want users to be able to form a reference to them. I want purely symbolic constants. Only enumeration types provide such named constants. The functionality of named constants is IHMO-cool. Two solutions : either invent something new and ultimately deprecate enums that are still part of C, or lift the restrictions on the underlying types of enumeration types. If I aim to ultimately deprecate enumeration types, then I aim to toss away the very existing functionality I am looking for. And since literal types are only required to declare the enumerators and not the type itself, just allow any type as underlying type.

#2 I am not entirely happy with the current state of the metaclasses proposal (P0707R2). The proposal includes an enum_class metaclass example that uses your technique (static constexpr member variables) to define the symbolic constants. Well, where did the IMHO-cool "named constants" functionality from C disappear? The metaclasses proposal won't let you define named constants in a class type because class types can't have any. Instead, if we could make enumeration type- and class type declarations uniform (backward-compatibly), we could define user-defined types declared with class, struct, enum, enum class/struct  with a metaclass each.

Nicol Bolas

unread,
Dec 23, 2017, 4:27:38 PM12/23/17
to ISO C++ Standard - Future Proposals


On Saturday, December 23, 2017 at 3:33:41 PM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 20:16:58 UTC+1, Nicol Bolas a écrit :


On Saturday, December 23, 2017 at 1:18:55 PM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 18:27:26 UTC+1, Nicol Bolas a écrit :
in your case, `is_enum_v<strong_equality>` is true; in my case, it is not.


That is entirely true. But I think I invalidated the stated reason why enum class was not used to create a strong typedefs in this paper.

... how? `enum class` cannot do what you did; the standard doesn't allow that.

Now, you can say that, if your proposal is adopted, you could use `enum class` for the results of `operator<=>`. But until your proposal is actually adopted, the reasons that `operator<=>` didn't use them are still quite valid. So you've invalidated nothing.

And even then, your proposal isn't good enough. Why? Because `strong_equality{10}` would still have to be legal code for your `enum class`.

Oh, I'm sure you'll now add some syntax to your "proposal" that explicitly disables such conversion (perhaps an `= delete` constructor or some such).

Correct. Good to read that, besides my consistent failure to convey my ideas clearly, you still can trace through the same path of thought that I followed. It must not be thinking so absurdly after all.

My point was that your idea is not fully formed; it's a sketch of an idea. Every time someone points out that part of your sketch hasn't been filled in, you hastily color it in with crayon.

But that's just another reminder that `enum class` is the wrong tool for strong aliases.

No. That's actually a reminder that `enum class` and `enum` need to go from half-baked to well-done, or be deprecated altogether.

Or we can just leave the perfectly useful idea of enumerators right where it is. There is nothing "half-baked" about them; they do exactly what they need to, and they do that thing quite well.

You're trying to hijack an existing syntax rather than creating a good one.

No. I'm trying to demonstrate that the syntax for class types and enumeration types can be made the same. (although I am not entirely happy with what I proposed in my OP)

So what if they could be? That doesn't mean they should be.

There is no reason to use `enum class` for inducing a strong alias than any other syntax.

Correct also. So let's not do that. Leave strong typedefs aside. Instead, let's do two things :

#1 I'd like to be able to enumerate named constants (§10.2) of any literal type, not just integral types. I could declare inline const constexpr member variables in a class type, but I don't want users to be able to form a reference to them.

Why would you care if users reference them or not? Do you really think that's going to impact the compiled result so much?

I want purely symbolic constants. Only enumeration types provide such named constants. The functionality of named constants is IHMO-cool. Two solutions : either invent something new and ultimately deprecate enums that are still part of C, or lift the restrictions on the underlying types of enumeration types. If I aim to ultimately deprecate enumeration types,

... why would C++ ever need to deprecate a perfectly useful, entirely reasonable, easily understood, and widely used piece of functionality like enumerations? What would it gain users to have to rewrite all of their code just to get the exact same behavior they had before?

Deprecating enumerations achieves nothing positive for the language. It doesn't even reclaim keywords or syntax for future use. It's an idea that has only downsides.

then I aim to toss away the very existing functionality I am looking for. And since literal types are only required to declare the enumerators and not the type itself, just allow any type as underlying type.

#2 I am not entirely happy with the current state of the metaclasses proposal (P0707R2). The proposal includes an enum_class metaclass example that uses your technique (static constexpr member variables) to define the symbolic constants. Well, where did the IMHO-cool "named constants" functionality from C disappear?

Nowhere. The `enum_class` metaclass is merely a proof-of-concept that shows the potential of the metaclasses proposal. It is not a suggestion that `enum class` or `enum` ought to be removed or shouldn't have been standardized; it merely shows that metaclasses can get you 99% of what they already provide.

Laurent LA RIZZA

unread,
Dec 23, 2017, 5:15:53 PM12/23/17
to ISO C++ Standard - Future Proposals

Nicol Bolas : OK, you win.

Vicente J. Botet Escriba

unread,
Dec 30, 2017, 6:29:29 AM12/30/17
to std-pr...@isocpp.org, Nicol Bolas
Le 23/12/2017 à 18:14, Nicol Bolas a écrit :
On Saturday, December 23, 2017 at 11:55:42 AM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 16:37:41 UTC+1, Nicol Bolas a écrit :
 
Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

Yes, that was exactly what I was referring to, and yes, it was a mistake.

I still don't know, that does not change the fact that even before that "mistake", people were using enum class to create strong typedefs of integral types.

People also used template instantiation to do compile-time computations. C++ programmers are willing to use any feature for any purpose, so long as it works. That doesn't mean we should enshrine it as the right idea. If people want to do compile-time computations, we should give them functions that run at compile-time, not make them jump through hoops of type instantiation and so forth.

You're basically saying, "we have template metaprogramming; why not add more to that, instead of bothering with this whole `constexpr` function thing?" Or "we have SFINAE; why not add more to that, instead of bothering with this whole `requires` thing?"
I believe all of us want some simplified form to build strong types (in particular those based on an underlying type).
enum class is the closest construct we have in C++17, so it is not weird to start from them.

Let use another syntax then
new class B : A {};


We have strong typedefs in C++17.

I wouldn't use the term strong typedefs, but strong types.

No, we have hacks in C++17.
enum class defines a new strong type, different from its underlying type. This is not a hack IMO.

<snip>



Can you please elaborate the use cases you think of where you would want other aliasing strength than the binary "they're the same type/they're not the same type"?

Go read every strong typedef proposal that has ever been proposed. They do a good job of covering the myriad of possible ways people want to deal with such things.

I have done (a long time ago), and will do again. I thank you for expressing your opinions on my ideas.

However, as a simple example, let's say you have a class A, and you make a strong typedef of it called B.

OK, so :

enum class B : A {};

Can B do everything that A could do?

No. It can be explicitly convertible to and from A (with and without variable numbers of ampersands, but I haven't thought it through) If A is literal, you can define named constants of type B. You can add also add member non-variables.
Literals of type A cannot be literals of type B, clearly.

While I can find useful a new strong type is explicitly convertible to the underlying type, I believe that sometimes the new strong type could be implicitly convertible to the underlying type, or even that the explicit conversion is not required, as e.g. chrono::duration. Have you considered this case? What about having only the explicit construction and a the ability to get the underlying representation using a specific function, e.g. underlying?

new class B : A {};

new class B : A {
public:
    operator A() = default;
};

new class B : A {
public:
    explicit operator A() = default;
};



 
For example, if A was Swappable, that means A probably has a non-member `swap` function.

enum class B : A {
public :
  friend void swap(B&, B&) = default;
};

Does B automatically get one?

No.

Should it?

No.

If A was addable to integers via non-member `operator+`, should B be addable too?

No, but :

enum class B : A {
public :
  friend B operator+(const B&, int) = default;
};

And if you can't automatically get the non-member interfaces of A, then what good is B?
 
B is a distinct type from A, explicitly convertible to and from it. B is stored the same way as A. It is as copyable, movable, assignable and destructible as A, except the signatures get B in lieu of A.


Lets see what can be done today without any new language change. There are some libraries proposing some kind of strong type helpers. E.g.


template <class Tag, class UT = int>
using strong_id = new_type<Tag, UT
      , meta_mixin::comparable<>
      , meta_mixin::hashable<>
      , meta_mixin::streamable<>
>;

would define an identifier that is comparable, hashable and streamable. (See https://github.com/viboes/std-make/blob/master/include/experimental/fundamental/v3/strong/strong_id.hpp and the same folder for more examples)

This needs of course the definition of meta_mixin::comparable<> that implement via a library the =default trampolines.


How the new language feature could take in account the ability to compose the inherited functionalities?
I believe metaclasses could help on this, but I have not tried anything with.


But it's not actually an alias of `A`, since it cannot do what `A` does.
Why do you want `B` to be an alias of `A`?


My point is that C++ has no mechanism to identify free functions that are a fundamental part of a class's interface from free functions which are not. And without that, making proper strong aliases becomes a maintenance burden. If someone adds a new free function interface to A, you have to add a `friend` declaration to every strong alias of it in order to pick up that behavior.
A strong type is a type that defines its operations, independently of the operations provided by its representation.


My other point is that you're spending way too much time on the syntax (insisting on overloading "enum class" for things that aren't enumerators) and not enough on the actually useful parts of the feature. For example, this is the first thing you've mentioned about this `= default` syntax for inheriting functionality from the class being aliased.
There is no alias.

That's a really important part of the proposal, and yet you didn't even mention that in your initial post, instead spending time trying to justify "enum class" as the correct induction syntax.

Why do you care so much that it gets called "enum class" instead of, like, anything else?
I agree, the syntax is not so important, but at the end we need it and if possible we should take in account the current language, so that the new feature extends properly the current one.


You can't even have them non-automatically in C++17, since you can't define B if A is not an integral type.

A question for you : if using enum class for creating strong aliases of integral types in C++17 is a mistake, how do you create strong aliases of integral types in C++17?

The same way I did in C++14: with an actual class. It's verbose, but at least it isn't stupid looking and confusing.
Lets explore both alternatives.

Vicente

Nicol Bolas

unread,
Dec 30, 2017, 11:30:27 AM12/30/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com
On Saturday, December 30, 2017 at 6:29:29 AM UTC-5, Vicente J. Botet Escriba wrote:
Le 23/12/2017 à 18:14, Nicol Bolas a écrit :
On Saturday, December 23, 2017 at 11:55:42 AM UTC-5, Laurent LA RIZZA wrote:


Le samedi 23 décembre 2017 16:37:41 UTC+1, Nicol Bolas a écrit :
 
Can we fix construction please?" Was adoption a mistake? Maybe, I don't know.

Yes, that was exactly what I was referring to, and yes, it was a mistake.

I still don't know, that does not change the fact that even before that "mistake", people were using enum class to create strong typedefs of integral types.

People also used template instantiation to do compile-time computations. C++ programmers are willing to use any feature for any purpose, so long as it works. That doesn't mean we should enshrine it as the right idea. If people want to do compile-time computations, we should give them functions that run at compile-time, not make them jump through hoops of type instantiation and so forth.

You're basically saying, "we have template metaprogramming; why not add more to that, instead of bothering with this whole `constexpr` function thing?" Or "we have SFINAE; why not add more to that, instead of bothering with this whole `requires` thing?"
I believe all of us want some simplified form to build strong types (in particular those based on an underlying type).
enum class is the closest construct we have in C++17, so it is not weird to start from them.

Yes, it is weird to start from them. Enumerations are strongly typed because it helps prevent errors. It stops you from accidentally using the wrong enumerator in the wrong place, and it stops you from accidentally pretending that an integer is an enumerator value. That's why enumerations are strongly typed.

That underlying types of enumerations are similar in concept to the relationship between a strong alias and the type it is based on is essentially a coincidence. They may be similar in form, but they're completely dissimilar in function.

Let use another syntax then
new class B : A {};


We have strong typedefs in C++17.

I wouldn't use the term strong typedefs, but strong types.
No, we have hacks in C++17.
enum class defines a new strong type, different from its underlying type. This is not a hack IMO.

That's not the hack part. The hack part is allowing `enumeration{integer}` to work on empty enumerations. Normally, to convert an integer to a strong enum type, you have to explicitly do a cast. Constructor syntax explicitly is disallowed. Until C++17, where it is allowed for empty enumerations.
 
  
For example, if A was Swappable, that means A probably has a non-member `swap` function.

enum class B : A {
public :
  friend void swap(B&, B&) = default;
};

Does B automatically get one?

No.

Should it?

No.

If A was addable to integers via non-member `operator+`, should B be addable too?

No, but :

enum class B : A {
public :
  friend B operator+(const B&, int) = default;
};

And if you can't automatically get the non-member interfaces of A, then what good is B?
 
B is a distinct type from A, explicitly convertible to and from it. B is stored the same way as A. It is as copyable, movable, assignable and destructible as A, except the signatures get B in lieu of A.


Lets see what can be done today without any new language change. There are some libraries proposing some kind of strong type helpers. E.g.


template <class Tag, class UT = int>
using strong_id = new_type<Tag, UT
      , meta_mixin::comparable<>
      , meta_mixin::hashable<>
      , meta_mixin::streamable<>
>;

would define an identifier that is comparable, hashable and streamable. (See https://github.com/viboes/std-make/blob/master/include/experimental/fundamental/v3/strong/strong_id.hpp and the same folder for more examples)

This needs of course the definition of meta_mixin::comparable<> that implement via a library the =default trampolines.

The problem with trampolines is that they're only half-solutions. They only work if you can identify up-front all of the interfaces you want to forward.

The ability to forward arbitrary interfaces is not available.
 
How the new language feature could take in account the ability to compose the inherited functionalities?
I believe metaclasses could help on this, but I have not tried anything with.

But it's not actually an alias of `A`, since it cannot do what `A` does.
Why do you want `B` to be an alias of `A`?

... because that's the premise of the discussion. We're talking about the behavior of strong aliases. Therefore, such a discussion involves two types, with one of them being a strong alias of the other.

I don't know what you mean with this question.

My point is that C++ has no mechanism to identify free functions that are a fundamental part of a class's interface from free functions which are not. And without that, making proper strong aliases becomes a maintenance burden. If someone adds a new free function interface to A, you have to add a `friend` declaration to every strong alias of it in order to pick up that behavior.
A strong type is a type that defines its operations, independently of the operations provided by its representation.

Then what you are talking about is a subobject, not an alias. A strong alias is a type that behaves to a large degree like an existing type, while still being a distinct type (and thus different from a `using name = type;` alias) that can add new operations or change existing ones.

The very concept of an alias is that the new typename's properties are defined primarily by an existing type. With weak aliases, they are the same type with a different name. With strong aliases, they are a distinct type. With subobjects, you're creating a distinct type, but its relationship with any other type is entirely a matter of how it is implemented.

That's not an alias.
Reply all
Reply to author
Forward
0 new messages