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)
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.
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.
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"?
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"?
--
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.
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.
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?
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?
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;
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".
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).
in your case, `is_enum_v<strong_equality>` is true; in my case, it is 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.
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.
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.
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?
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.
<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.
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.
Le 23/12/2017 à 18:14, Nicol Bolas a écrit :
I believe all of us want some simplified form to build strong types (in particular those based on an underlying type).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?"
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 {};
I wouldn't use the term strong typedefs, but strong types.
We have strong typedefs in C++17.
enum class defines a new strong type, different from its underlying type. This is not a hack IMO.No, we have hacks in C++17.
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.
Why do you want `B` to be an alias of `A`?But it's not actually an alias of `A`, since it cannot do what `A` does.
A strong type is a type that defines its operations, independently of the operations provided by its representation.
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.