You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Initializer as a statement: generated code does not compile #131
It feel weird that initializer is a statement, particularly when the generating code is treating it as if it were an expression: e.g., here, and here.
Edit: another problem I see with this approach is that it is not clear how should semantics of an initializer be described in a language specification.
But there's a method to my madness here... it is deliberate that every initializer is a statement, regardless of whether the declaration is of an object, function, or (soon) type: The legal grammar for the initializer of all of them is identical, with semantic rules allowing a subset of the grammar for each.
For example, the type declaration syntax I'm planning to add soon looks like this:
shape: type = {
// ordinary declarations, for example:
// ordinary object declaration
x: int = 0; // nonstatic data member
y: int = 0;
// ordinary function declarations, with explicit "this" and "that" parameters
// that default to ": mytype" so you don't have to write that
draw: (this, on: canvas) = { /*...*/ } // nonstatic member function (has a 'this')
move: (inout this, offset: position) = { /*...*/ } // another, this time mutable 'this' (aka non-const)
register: (obs: *observer) = { /*...*/ } // static member function (doesn't have a 'this')
operator=: (out this, that) = { /* see d0708, this covers all of {copy,move}x{ctor,assign} */ }
// ordinary type declaration (nested, and using a metaclass function example)
colors: type is enum = {
red := 0; // ordinary object declaration syntax again
green; // for convenience can allow simple _expression-statements_ that contain only an identifier, and let the metaclass transform it
blue;
}
// etc.
}
Goal: "Yes, you already pretty much know how to write classes, once I implement those."
Note this looks (and parses) just like a function body, except it has no computation/iteration statements (yet; this leaves the door open to adding top-level class statements someday if that's useful)... the type initializer must be a compound-statement (i.e., {} enclosed), that contains declaration-statements (i.e., ordinary declarations)
Note this works because in Cpp2's design (disclaimer: implementation still in progress):
this is an explicit ordinary parameter; there is no special/weird member function declaration grammar
base classes will declared and initialized the same as data members (including they have names, and this has other advantages including layout and initialization control)
The only grammar extensions I plan to add beyond what's already implemented are:
an access-specifier on any declaration-statement (allowed only in type initializers)
a way to specify an object declaration is a base, which might be spelled is (allowed only in type initializers).
a this-specifier, which can be implicit for implicit conversion functions, or virtual, override, final for virtual functions (allowed only on this parameters) ... you'll see this is already in the parse.h grammar comments for parameter-declaration
So, yes, it's deliberate that everything in Cpp2 is grammatically defined/initialized with a statement, i.e., an applicable subset of the statement grammar. My hope is that it will be consistent and perhaps even elegant.
I'm not sure this is the place to ask, but I'm only seeing/interacting with this project via email at the moment, so as this is the first time I have seen this mentioned,
Will it be possible/please could it be possible to state that our type satisfies certain concepts, e.g.? "circularBuffer: type is std::ranges::range"
I believe it could be converted into a static cast following the cpp1 class declaration
I believe it would be beneficial to api users because there is a large hint as to how the type is expected to be used coupled tightly with its declaration
I believe it would be beneficial to implementors as it provides a built in compile time guarantee/test.
If this requires overloads for is (but not as) then I can see the benefit of being able to constexp if or inspect with concepts like this too.
I cannot however justify off the top of my head how it would reduce teaching requirements, or problems it would solve. It just seems like something that fits nicely with the new grammar.
The same effect could be achieved with class level static statements, so that the next line under the type declaration you could write a static assert to the same effect.
In a nutshell: Yes, and more... not just asserting the type meets requirements, but actually actively generating the intended kind of type. See the talks and papers at README.md > 2017: Reflection, generation, and metaclasses, particularly paper P0707, "Metaclass functions: Generative C++". If you like videos, I can recommend the beginning of the ACCU video which is the only time I've ever done a live pseudo-"usability study" in front of a live audience with genuinely unprepared volunteers. Thanks again to the volunteers and ACCU!
Fascinating, thank you,
Does this mean that cpp2 aims to unify C++ typing even further by making enums, structs, and unions meta classes rather than distinct language features?
I did notice however in the enum meta classes section you didn't give an example of usage. func(MetaEnum val) wouldn't work because in the case of a meta class enum MetaEnum is a typename, and the enumeration values are all member variables of some type. func(MetaEnum::value_t val) where I assume value_t = reflexpr(underlying).as(new_type) to maintain strong typing. And while that works, func(MetaEnum::value_t val) feels like a step back when everything else is a step forward. Perhaps a necessary sacrifice for the rest of the progress, but perhaps you've an inkling of how to solve it in cpp1 or cpp2 syntax?
On 28 December 2022 18:40:51 Herb Sutter ***@***.***> wrote:
@SebastianTroy<https://github.com/SebastianTroy> Great question.
In a nutshell: Yes, and more... not just asserting the type meets requirements, but actually actively generating the intended kind of type. See the talks and papers at README.md > 2017: Reflection, generation, and metaclasses<https://github.com/hsutter/cppfront#2017-reflection-generation-and-metaclasses>, particularly paper P0707, "Metaclass functions: Generative C++"<https://wg21.link/p0707>. If you like videos, I can recommend the beginning of the ACCU video<https://www.youtube.com/watch?v=6nsyX37nsRs> which is the only time I've ever done a live pseudo-"usability study" in front of a live audience with genuinely unprepared volunteers. Thanks again to the volunteers and ACCU!
—
Reply to this email directly, view it on GitHub<#131 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQJ3C7MA6OFCDLRBOF3WPSCS7ANCNFSM6AAAAAASH7KVSU>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
Does this mean that cpp2 aims to unify C++ typing even further by making enums, structs, and unions meta classes rather than distinct language features?
Exactly.
I did notice however in the enum meta classes section you didn't give an example of usage. func(MetaEnum val) wouldn't work because in the case of a meta class enum MetaEnum is a typename, and the enumeration values are all member variables of some type. func(MetaEnum::value_t val) where I assume value_t = reflexpr(underlying).as(new_type) to maintain strong typing. And while that works, func(MetaEnum::value_t val) feels like a step back when everything else is a step forward.
It sounds like you're mainly concerned with having to utter the name of the enum :: the enumerator, i.e., the enumerators are scoped inside the enum? I view that as a feature rather than a bug, because C enums inject their enumerator names into the enclosing scope which litters it. This is it's one of the reasons I, Bjarne Stroustrup, and David Miller designed C++11 enum class. The proposal paper N2347 goes into the rationale. For convenience, C++20 added using enum. Does that help address your questions, even if you may still prefer the C enumerator non-qualification convenience?
No, I actually prefer uttering the scope of the enum, it was more that when defining a function that accepts an enum value, we currently saw which enum the value will belong to, it was written as if the enum itself was a type with a set of values,
enum class Shape { Circle, Square };
void foo(Shape s) { ...}
vs with a meta class, we get a type with a number of members,
class(basic_enum) Shape { auto Circle, Square; };
void foo(Shape s) { ... } // no longer makes sense, we want the user to specify one of Shape's members, not an instance of Shape!
void foo(decltype(Shape::Circle) s) { ... } // more verbose and we lose type safety
Our meta class can inject the following typedef
enum_t = reflexpr(dexltype(Circle)).as(new_type);
(Where new_type is an empty metaclass, essentially creating a new type identical to the underlying type, but treated as a seperate type by the compiler)
void foo(Shape::enum_t s) { ... } // slightly more verbose and still different to current enum usage in C++ and any language I've learnt so far,
This extra step just adds just enough friction that I can see there being opposition to it.
Anyway, thank you for taking the time to personally respond, but I think I've had enough attention, I'm more keen to see the fruits of your type implementation labour's, as I'm sure everyone else is!
On 30 December 2022 19:33:10 Herb Sutter ***@***.***> wrote:
Does this mean that cpp2 aims to unify C++ typing even further by making enums, structs, and unions meta classes rather than distinct language features?
Exactly.
I did notice however in the enum meta classes section you didn't give an example of usage. func(MetaEnum val) wouldn't work because in the case of a meta class enum MetaEnum is a typename, and the enumeration values are all member variables of some type. func(MetaEnum::value_t val) where I assume value_t = reflexpr(underlying).as(new_type) to maintain strong typing. And while that works, func(MetaEnum::value_t val) feels like a step back when everything else is a step forward.
It sounds like you're mainly concerned with having to utter the scope of the enum? I view that as a feature rather than a bug, because C enums inject their enumerator names into the enclosing scope which litters it. This is it's one of the reasons I, Bjarne Stroustrup, and David Miller designed C++11 enum class<https://en.cppreference.com/w/cpp/language/enum>. The proposal paper N2347<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf> goes into the rationale. For convenience, C++20 added using enum<https://en.cppreference.com/w/cpp/language/enum#Using-enum-declaration>.
—
Reply to this email directly, view it on GitHub<#131 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQKIVDMKFZI67FRMCVLWP42HFANCNFSM6AAAAAASH7KVSU>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
class(basic_enum) Shape { auto Circle, Square; };
voidfoo(Shape s) { ... } // no longer makes sense, we want the user to specify one of Shape's members, not an instance of Shape!
It seems to me like you're having issues with your own interpretation of basic_enum. I'd expect basic_enum to transform Shape into something like
classShape {
staticconstexpr Shape Circle = {0};
staticconstexpr Shape Square = {1};
int underlying;
};
which would work more like today's enum class. Although you can't have data members of Shape, since it's an incomplete type.
Referring to P0707r4 section 3.8, this example is named enum_class (to avoid the Cpp1 keyword(s)) and there's also a flag_enum (we really want both to be first-class and have different names). Injecting the value instance is done in the basic_enum metaclass function that is common to both enum_class and flag_enum, after computing the proper type (using the type the user specific, or a default type if they didn't specify one):
// from inside P0707r4 basic_enum()'s code (note: speculative evolution of today's C++ syntax)
->(target) {
U value; // the instance value
}
Pasting some of the examples from 3.8:
[[Note: not Cpp2 syntax, this is from P0707]]
Here is a state enumeration that starts at value 1 and counts up:
class(enum_class) state {
auto started = 1, waiting, stopped; // type is int
};
state s = state::started;
while (s != state::waiting) {
// ...
}
Here is a different enumeration using a different value type and setting some values while using incremented
values where those are useful:
In Cpp2, I aim to write those with roughly this syntax:
state: type is enum = {
started := 1; waiting; stopped;
};
s := state::started;
while s != state::waiting {
// ...
}
skat_games: type is enum = {
diamonds: i8 = 9; hearts /*10*/; spades /*11*/; clubs /*12*/; grand = 24;
};
Anyway, thank you for taking the time to personally respond, but I think I've had enough attention, I'm more keen to see the fruits of your type implementation labour's, as I'm sure everyone else is!
Thanks for the kind words, and no worries! I respond on purpose because it's worth the time to reply to to good questions == ones that I think others will have, so they can see the answers too and future readers can reference these answers. The more common ones should be promoted to a Design Note on this repo's wiki, but even having a reply here in the comments makes them referenceable. And it's nice to take a break from coding to write English, and vice versa. :)
Also, writing a few of these answers helps preserve a repoint-in-time design snapshot, a self-documenting record of intentions and ideas that I can refer to if I want to look up the design archaeology if this part of the design evolves further in the future.
Thanks!
Reacted by Johel Ernesto Guerrero Peña and Filip Sajdak
I think you're missing my point, and I'm not sure how else to eloquate it...
The function definition
void foo(MyEnum val);
Means two completely different things depending on whether MyEnum is an enum/enum class, or a meta class enum approximation. I believe Herb is keen to avoid similar things being spelled differently, or different things being spelled similarly.
With an enum or enum class, this function accepts a copy of one of the enum's values, or any value of the underlying type statically casted to MyEnum.
With an enum meta class that same function signature means it accepts a copy of an instance of a MyEnum meta class, when in fact we want the function to accept one of MyEnum's static members' values,
You (and I) suggested that the meta class could have an underlying member. This would naturally lead to the following function declaration,
void foo(MyEnum::underlying val);
A departure from current syntax and the syntax of any language I've come across in passing, and without as yet non-existent features, also loses enum class type safety
The Cpp2-declared type is enum behaves the same as a Cpp1-declared enum class. Both have a number of compile-time-constant named static enumerator values in their scopes, and a nonstatic dat amember that stores this object's value. All are of the enumeration's underlying type.
All the metaclass function is doing is generating the boilerplate, so you can write just this
state: type is enum = {
started := 1; waiting; stopped;
};
instead of writing out all of this (rough syntax, my current thought is to support only constexpr static data members and not support const nonstatic data members, so I don't think I need a static keyword)
// Writing it all by hand, instead of letting the 'enum' metaclass function do it for us
state: type = {
// "static constexpr" data members
started: const int = 1;
waiting: const int = 2;
stopped: const int = 3;
// nonstatic data member
value: int;
// construction
operator=: (out this, v: int) = { value = v; }
operator=: (out this, that) = { value = that.value; }
// comparison
operator<=>: (this, that) = { return value <=> that.value; }
// and maybe one or two more like a to_string function
};
Oh I see, the enum meta class has a non static member too, that's the detail I was missing. Sorry for dragging that out so much.
On 30 December 2022 21:59:41 Herb Sutter ***@***.***> wrote:
The Cpp2-declared enum behaves the same as a Cpp1-declared enum class. Both have a number of compile-time-constant named static enumerator values in their scopes, and a nonstatic dat amember that stores this object's value. All are of the enumeration's underlying type. Does that help?
—
Reply to this email directly, view it on GitHub<#131 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQL32QBPOUS7J5CKWF3WP5LMTANCNFSM6AAAAAASH7KVSU>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
Activity
hsutter commentedon Dec 28, 2022
Thanks! Yes, I should reject that code... done.
But there's a method to my madness here... it is deliberate that every initializer is a statement, regardless of whether the declaration is of an object, function, or (soon) type: The legal grammar for the initializer of all of them is identical, with semantic rules allowing a subset of the grammar for each.
For example, the type declaration syntax I'm planning to add soon looks like this:
Goal: "Yes, you already pretty much know how to write classes, once I implement those."
Note this looks (and parses) just like a function body, except it has no computation/iteration statements (yet; this leaves the door open to adding top-level class statements someday if that's useful)... the type initializer must be a compound-statement (i.e.,
{}enclosed), that contains declaration-statements (i.e., ordinary declarations)Note this works because in Cpp2's design (disclaimer: implementation still in progress):
thisis an explicit ordinary parameter; there is no special/weird member function declaration grammarThe only grammar extensions I plan to add beyond what's already implemented are:
is(allowed only in type initializers).implicitfor implicit conversion functions, orvirtual,override,finalfor virtual functions (allowed only onthisparameters) ... you'll see this is already in theparse.hgrammar comments for parameter-declarationSo, yes, it's deliberate that everything in Cpp2 is grammatically defined/initialized with a statement, i.e., an applicable subset of the statement grammar. My hope is that it will be consistent and perhaps even elegant.
SebastianTroy commentedon Dec 28, 2022
hsutter commentedon Dec 28, 2022
@SebastianTroy Great question.
In a nutshell: Yes, and more... not just asserting the type meets requirements, but actually actively generating the intended kind of type. See the talks and papers at README.md > 2017: Reflection, generation, and metaclasses, particularly paper P0707, "Metaclass functions: Generative C++". If you like videos, I can recommend the beginning of the ACCU video which is the only time I've ever done a live pseudo-"usability study" in front of a live audience with genuinely unprepared volunteers. Thanks again to the volunteers and ACCU!
SebastianTroy commentedon Dec 30, 2022
hsutter commentedon Dec 30, 2022
Exactly.
It sounds like you're mainly concerned with having to utter the name of the enum
::the enumerator, i.e., the enumerators are scoped inside the enum? I view that as a feature rather than a bug, because Cenums inject their enumerator names into the enclosing scope which litters it. This is it's one of the reasons I, Bjarne Stroustrup, and David Miller designed C++11enum class. The proposal paper N2347 goes into the rationale. For convenience, C++20 addedusing enum. Does that help address your questions, even if you may still prefer the C enumerator non-qualification convenience?SebastianTroy commentedon Dec 30, 2022
JohelEGP commentedon Dec 30, 2022
It seems to me like you're having issues with your own interpretation of
basic_enum. I'd expectbasic_enumto transformShapeinto something likewhich would work more like today's
enum class. Although you can't have data members ofShape, since it's an incomplete type.hsutter commentedon Dec 30, 2022
[Edited now that I realize @JohelEGP was replying to @SebastianTroy, not me]
@JohelEGP Right.
Referring to P0707r4 section 3.8, this example is named
enum_class(to avoid the Cpp1 keyword(s)) and there's also aflag_enum(we really want both to be first-class and have different names). Injecting the value instance is done in thebasic_enummetaclass function that is common to bothenum_classandflag_enum, after computing the proper type (using the type the user specific, or a default type if they didn't specify one):Pasting some of the examples from 3.8:
In Cpp2, I aim to write those with roughly this syntax:
Does that address what you were wondering about?
hsutter commentedon Dec 30, 2022
@SebastianTroy
Thanks for the kind words, and no worries! I respond on purpose because it's worth the time to reply to to good questions == ones that I think others will have, so they can see the answers too and future readers can reference these answers. The more common ones should be promoted to a Design Note on this repo's wiki, but even having a reply here in the comments makes them referenceable. And it's nice to take a break from coding to write English, and vice versa. :)
Also, writing a few of these answers helps preserve a repoint-in-time design snapshot, a self-documenting record of intentions and ideas that I can refer to if I want to look up the design archaeology if this part of the design evolves further in the future.
Thanks!
SebastianTroy commentedon Dec 30, 2022
hsutter commentedon Dec 30, 2022
The Cpp2-declared
type is enumbehaves the same as a Cpp1-declaredenum class. Both have a number of compile-time-constant named static enumerator values in their scopes, and a nonstatic dat amember that stores this object's value. All are of the enumeration's underlying type.All the metaclass function is doing is generating the boilerplate, so you can write just this
instead of writing out all of this (rough syntax, my current thought is to support only
constexprstatic data members and not supportconstnonstatic data members, so I don't think I need astatickeyword)Does that help?
SebastianTroy commentedon Dec 30, 2022
Reject object initializers that are not expressions, closes hsutter#131
Reject object initializers that are not expressions, closes hsutter#131