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
[SUGGESTION] Add support for defining type aliases. #273
Recently cppfront added the ability to define user-defined types. I want to extend this feature to be able to declare type aliases. My proposed syntax looks like this:
DWORD: type = u32;
// templated type aliases
MyPair: <T> type = std::pair<T, T>;
// nested aliases work too
MyVector: <T> type = {
size_type: type = std::size_t;
}
Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?
No.
Will your feature suggestion automate or eliminate X% of current C++ guidance literature?
Yes, this syntax uses the l-to-r declaration syntax already used widely in Cpp2, so there's no need to teach the syntax using alias_name = type_name.
I have already implemented this feature locally so I can provide a PR.
Nice feature, but one thing I'd like to point out, it's important to have distinction between actual user defined types and type aliases. The syntax could be same but we can have a different keyword instead of type.
One example could be typealias, so the given examples would be
DWORD: typealias = u32;
// templated type aliases
MyPair: <T> typealias = std::pair<T, T>;
// nested aliases work too
MyVector: <T> typealias = {
size_type: typealias = std::size_t;
}
Any keyword that fits can be chosen but it's the distinction which I emphasize that should be present.
@AbhinavK00 You make a very good point and I've actually thought about this while implementing.
Currently in my implementation, if initializer.statement is a statement_node::compound, cppfront prints a new class. If it's a statement_node::expression (no braces) then cppfront prints using ..., but I felt this approach was very hacky.
If the distinction between UDTs and type aliases is significant enough to warrant an introduction of a new keyword, I could try implementing it that way as well.
Nice feature, but one thing I'd like to point out, it's important to have distinction between actual user defined types and type aliases. The syntax could be same but we can have a different keyword instead of type. One example could be typealias, so the given examples would be
DWORD: typealias = u32;
// templated type aliases
MyPair: <T> typealias = std::pair<T, T>;
// nested aliases work too
MyVector: <T> typealias = {
size_type: typealias = std::size_t;
}
Any keyword that fits can be chosen but it's the distinction which I emphasize that should be present.
I've read in a comment from @hsutter that he wants to use is keyword for class inheritance, maybe is can be a good candidate for type aliases too, because a type alias is exactly the same as the other type without any additional definition. I don't know the syntax of class inheritance yet, but maybe it would be something like this:
DWORD: type is u32;
Edit: Also is keyword doesn't make a good distinction between user defined types and type aliases. I withdraw it! In this case, = seems more natural than is keyword.
Good suggestion. I do intend to implement aliases down the road, and I haven't settled on a syntax for them yet.
One issue with DWORD: type = u32; is that, while it's not legal today and therefore is available, it could close a door to future development because it's a syntax we might naturally use for strong typedefs (the syntax implies "declare a new separate thing initialized from this existing thing," like with x: int = 42; where x is not an alias for 42, it's a copy).
I am considering using is declaratively in a couple of places, such as to declare base classes (coming soon). Here's the key test I'm using to decide whether to use is declaratively: If you declare something with is, then it should be guaranteed to later evaluate true if you ask the equivalent is test, and that is test should be supported. In this case, if DWORD is an alias for u32, then if we make DWORD is u32 be true (not currently supported, but reasonable), then a syntax that includes is would be a candidate for declaring the alias.
Nice feature, but one thing I'd like to point out, it's important to have distinction between actual user defined types and type aliases. The syntax could be same but we can have a different keyword instead of type.
No real motivation I guess but there should be a distinction between strong typedefs and just aliases to typenames.
Consider the two syntaxes considered above,
DWORD : typealias = i32;
This is just another name and DWORD can be used anywhere in place of u32, this doesn't have many use cases.
Then there's this,
DWORD : type is i32;
This is like wrapping an i32 in a struct, it's different from i32, can be casted to an i32 and need to be casted explicitly if using it in place of an i32 and vice-versa.
So no real motivation, but I just put it out in case if others agree.
One issue with DWORD: type = u32; is that, while it's not legal today and therefore is available, it could close a door to future development because it's a syntax we might naturally use for strong typedefs (the syntax implies "declare a new separate thing initialized from this existing thing," like with x: int = 42; where x is not an alias for 42, it's a copy).
But what is a "strong typedef"? It'd be great if, down the road of development of Cpp2, we could find the answer meant for Cpp2. Although, with metaclasses, we might be able to author better answers than today's Cpp1.
As a library author, when you export a type name sometimes it refers to a class you defined, and sometimes you want it to be just an alias to something else, without leaking that detail to the user of the library.
In current C++ the leakage is unavoidable, for example because the user can forward-declare your classes, but not an alias. Hopefully in Cpp2 this specific case is not an issue because forward-declaring is made unnecessary. But the general request remains, of letting a library author export an alias that is indistinguishable from a type's "true" name.
But what is a "strong typedef"?
I'm guessing Herb is thinking of compile-time modification of types, for which whether you're modifying the original or a copy makes a difference?
I want to handle aliases in general, not just type aliases -- also namespace, function, and object aliases. For example, namespace aliases already exist in Cpp1 and are useful. I don't want to lose that convenience, I just want to make it more regular with the rest of the language.
I want a consistent general syntax that works for all of these. So for example the word typealias is tempting, but that word would work well only for types, and then we'd be getting pressure to add namespacealias etc. too.
That syntax should be a declaration syntax, since we are declaring a new name -- it just happens to be an alias rather than a standalone entity. So the syntax should start with name :, but should somehow be different from the name : type = value Cpp2 uses for all declarations to express that this is an alias, not a standalone entity.
So after some thought of brainstorming options, include ones that include is, the one I like the best is to use exactly the declaration syntax, but with == to connote equality with an existing entity, instead of = which connotes setting the new entity's value. For example:
lit: namespace == ::std::literals;
pmr_vec: <T> type
== std::vector<T, std::pmr::polymorphic_allocator<T>>;
func :== some_original::inconvenient::function_name;
vec :== my_vector; // note: const&, aliases are never mutable
And the point about const& too, didn't get those.
Edit: Nevermind, got it
I didn't 🙂 Is it supposed to mean that you can't "assign" something to the alias to bind it to something else? (But you already can't assign anything to a type name). const vector& means the vector can't be written to, so I hope it's not that.
@AbhinavK00, Because = in identifier: /*something*/ = /*definition*/ either defines or redefines identifier. That's why we cannot add members to types after they are defined.
On the other hand, += is similar to extension methods in C#. It's like if this was possible in Cpp2:
point: type = {
x: int = 0;
y: int = 0;
operator=: (out this, arg_x: int, arg_y: int) = {
x = arg_x;
y = arg_y;
}
}
point: type += {
// Extension method
print: (this) = {
std::cout << x << ", " << y << '\n';
}
}
Namesapces inherently won't be redefined, so only new declarations are added to them. It's going to be like this example in a similar manner to the previous example:
// Maybe they should be defined at first with `=`
x: namespace= {}
x: namespace += {
func: () = {}
}
x: namespace += {
another_func: () = {}
}
main: () = {
x::func();
x::another_func();
}
Ignore the following
Unhide contents...
If Cpp2 could support extension methods, namespaces could be another metaclass function:
// Maybe they should be defined at first with `=`
x: @namespacetype= {}
x: @namespacetype += {
func: () = {}
}
It would merge namespace classes (types which only have static members) and namespaces to a single concept.
EDIT: Types and namespaces have logically different behaviours in many ways in Cpp2. So you can ignore this part.
Because = in identifier: /something/ = /definition/ either defines or redefines identifier
Then all remaining uses of : /signature/ = would denote definition of the non re-definable kind, which is a bigger plus.
if = is able to redefine, then anything declared by : /*something*/ = /*definition*/ should be redefinable but thats not true for functions. I like @JohelEGP 's definition better but variables are an exception to that, not sure about classes
Then all remaining uses of : /signature/ = would denote definition of the non re-definable kind, which is a bigger plus.
Varibles can be re-assigned so I don't think : /*signature*/ = denotes something of non-redefinable kind and you can also add functions to classes.
Variables can be assigned to, yes.
But only once, the first time, may it have : indicating definition.
Other assignments are not redefinitions.
Shadowing does redefine what the identifier means for the current scope.
And is distinguished from : namespace +=, which really does append, and doesn't shadow.
// Maybe they should be defined at first with `=`
x: namespace= {}
I disagree.
Maybe that should be the case for the extension methods you describe.
But : is definition, and namespaces always append, so requiring = first is just needless friction.
I disagree.
Maybe that should be the case for the extension methods you describe.
But : is definition, and namespaces always append, so requiring = first is just needless friction.
More precisely, : is for declaration, and the first = is for definition:
point: type = {
operator=: (out this, x: int, y: int) = {}
}
main: () = {
// It declares `x` is a `point`.
x: point;
// It defines `x` with constructor.// It generates: `x.construct(0, 0);`
x = (0, 0);
}
You're right. Requiring to define namespaces with = for the first time, can lead to complicated code.
More precisely, : is for declaration, and the first = is for definition:
I wonder if that's how you'd describe it in Cpp2.
The seemingly-Cpp2-declaration is lowered to Cpp1 as a definition.
Without documentation, I can only guess as to the terminology.
It seems plausible that the definite first assignment is the definition.
But I'm not convinced.
Thinking about definite first assignment made me remember that Cpp2 doesn't have forward declarations.
So perhaps it's not a matter of declaration vs. definition.
In Cpp2, things that can be without an initializer are:
Variable declarations, which require a definite first assignment.
Activity
filipsajdak commentedon Mar 11, 2023
I am currently missing that feature on the cpp2 side.
I like the syntax.
AbhinavK00 commentedon Mar 11, 2023
Nice feature, but one thing I'd like to point out, it's important to have distinction between actual user defined types and type aliases. The syntax could be same but we can have a different keyword instead of
type.One example could be
typealias, so the given examples would beAny keyword that fits can be chosen but it's the distinction which I emphasize that should be present.
leejy12 commentedon Mar 12, 2023
@AbhinavK00 You make a very good point and I've actually thought about this while implementing.
Currently in my implementation, if initializer.statement is a
statement_node::compound, cppfront prints a newclass. If it's astatement_node::expression(no braces) then cppfront printsusing ..., but I felt this approach was very hacky.If the distinction between UDTs and type aliases is significant enough to warrant an introduction of a new keyword, I could try implementing it that way as well.
msadeqhe commentedon Mar 12, 2023
I've read in a comment from @hsutter that he wants to use
iskeyword for class inheritance, maybeiscan be a good candidate for type aliases too, because a type alias is exactly the same as the other type without any additional definition. I don't know the syntax of class inheritance yet, but maybe it would be something like this:Edit: Also
iskeyword doesn't make a good distinction between user defined types and type aliases. I withdraw it! In this case,=seems more natural thaniskeyword.hsutter commentedon Mar 12, 2023
Good suggestion. I do intend to implement aliases down the road, and I haven't settled on a syntax for them yet.
One issue with
DWORD: type = u32;is that, while it's not legal today and therefore is available, it could close a door to future development because it's a syntax we might naturally use for strong typedefs (the syntax implies "declare a new separate thing initialized from this existing thing," like withx: int = 42;wherexis not an alias for42, it's a copy).I am considering using
isdeclaratively in a couple of places, such as to declare base classes (coming soon). Here's the key test I'm using to decide whether to useisdeclaratively: If you declare something withis, then it should be guaranteed to later evaluatetrueif you ask the equivalentistest, and thatistest should be supported. In this case, ifDWORDis an alias foru32, then if we makeDWORD is u32be true (not currently supported, but reasonable), then a syntax that includesiswould be a candidate for declaring the alias.jcanizales commentedon Mar 15, 2023
What's your motivation for this, @AbhinavK00?
AbhinavK00 commentedon Mar 15, 2023
No real motivation I guess but there should be a distinction between strong typedefs and just aliases to typenames.
Consider the two syntaxes considered above,
This is just another name and DWORD can be used anywhere in place of u32, this doesn't have many use cases.
Then there's this,
This is like wrapping an
i32in a struct, it's different fromi32, can be casted to ani32and need to be casted explicitly if using it in place of ani32and vice-versa.So no real motivation, but I just put it out in case if others agree.
JohelEGP commentedon Mar 15, 2023
But what is a "strong typedef"? It'd be great if, down the road of development of Cpp2, we could find the answer meant for Cpp2. Although, with metaclasses, we might be able to author better answers than today's Cpp1.
jcanizales commentedon Mar 20, 2023
As a library author, when you export a type name sometimes it refers to a class you defined, and sometimes you want it to be just an alias to something else, without leaking that detail to the user of the library.
In current C++ the leakage is unavoidable, for example because the user can forward-declare your classes, but not an alias. Hopefully in Cpp2 this specific case is not an issue because forward-declaring is made unnecessary. But the general request remains, of letting a library author export an alias that is indistinguishable from a type's "true" name.
I'm guessing Herb is thinking of compile-time modification of types, for which whether you're modifying the original or a copy makes a difference?
hsutter commentedon Apr 2, 2023
On thinking about this further, two things:
I want to handle aliases in general, not just type aliases -- also namespace, function, and object aliases. For example, namespace aliases already exist in Cpp1 and are useful. I don't want to lose that convenience, I just want to make it more regular with the rest of the language.
I want a consistent general syntax that works for all of these. So for example the word
typealiasis tempting, but that word would work well only for types, and then we'd be getting pressure to addnamespacealiasetc. too.That syntax should be a declaration syntax, since we are declaring a new name -- it just happens to be an alias rather than a standalone entity. So the syntax should start with
name :, but should somehow be different from thename : type = valueCpp2 uses for all declarations to express that this is an alias, not a standalone entity.So after some thought of brainstorming options, include ones that include
is, the one I like the best is to use exactly the declaration syntax, but with==to connote equality with an existing entity, instead of=which connotes setting the new entity's value. For example:Checking it in now...
AbhinavK00 commentedon Apr 2, 2023
Question: What does the following line mean?
And the point about
const&too, didn't get those.Also, these are same as
usingcounterparts to cpp, yes?Edit: Nevermind, got it
msadeqhe commentedon Apr 2, 2023
Thanks. Nice reasonable syntax.
I think
x :== y;is a short form for bothx: function == y;andx: variable == y;, because function and variable aliases are distinguishable fromy.Maybe also
zinx: z == y;could be optional for both namespaces and types.jcanizales commentedon Apr 3, 2023
I didn't 🙂 Is it supposed to mean that you can't "assign" something to the alias to bind it to something else? (But you already can't assign anything to a type name).
const vector&means the vector can't be written to, so I hope it's not that.17 remaining items
AbhinavK00 commentedon May 8, 2023
Varibles can be re-assigned so I don't think
: /*signature*/ =denotes something of non-redefinable kind and you can also add functions to classes.msadeqhe commentedon May 8, 2023
@JohelEGP, I like your idea.
@AbhinavK00, Because
=inidentifier: /*something*/ = /*definition*/either defines or redefinesidentifier. That's why we cannot add members to types after they are defined.On the other hand,
+=is similar to extension methods in C#. It's like if this was possible in Cpp2:point: type = { x: int = 0; y: int = 0; operator=: (out this, arg_x: int, arg_y: int) = { x = arg_x; y = arg_y; } } point: type += { // Extension method print: (this) = { std::cout << x << ", " << y << '\n'; } }Namesapces inherently won't be redefined, so only new declarations are added to them. It's going to be like this example in a similar manner to the previous example:
Ignore the following
Unhide contents...
If Cpp2 could support extension methods, namespaces could be another metaclass function:
It would merge namespace classes (types which only have static members) and namespaces to a single concept.
EDIT: Types and namespaces have logically different behaviours in many ways in Cpp2. So you can ignore this part.
AbhinavK00 commentedon May 8, 2023
if
=is able to redefine, then anything declared by: /*something*/ = /*definition*/should be redefinable but thats not true for functions. I like @JohelEGP 's definition better but variables are an exception to that, not sure about classesJohelEGP commentedon May 8, 2023
Variables can be assigned to, yes.
But only once, the first time, may it have
:indicating definition.Other assignments are not redefinitions.
Shadowing does redefine what the identifier means for the current scope.
And is distinguished from
: namespace +=, which really does append, and doesn't shadow.I disagree.
Maybe that should be the case for the extension methods you describe.
But
:is definition, and namespaces always append, so requiring=first is just needless friction.leejy12 commentedon May 8, 2023
@JohelEGP I like your idea.
I think it would be better if this discussion about namespaces (and namespace metaclass) were moved to a new, open issue for better visibility.
JohelEGP commentedon May 8, 2023
The entry bar is a bit higher for suggestions, compared to continue piling up on an issue's discussion. But I did it: #438.
msadeqhe commentedon May 8, 2023
I mean that
=can define or redefine identifiers in new scopes.Currently Cpp2 doesn't support local functions, but this is an example:
(This example is just my thought about how local functions may be available in the future.)
msadeqhe commentedon May 8, 2023
More precisely,
:is for declaration, and the first=is for definition:point: type = { operator=: (out this, x: int, y: int) = {} } main: () = { // It declares `x` is a `point`. x: point; // It defines `x` with constructor. // It generates: `x.construct(0, 0);` x = (0, 0); }You're right. Requiring to define namespaces with
=for the first time, can lead to complicated code.JohelEGP commentedon May 8, 2023
I wonder if that's how you'd describe it in Cpp2.
The seemingly-Cpp2-declaration is lowered to Cpp1 as a definition.
Without documentation, I can only guess as to the terminology.
msadeqhe commentedon May 8, 2023
A declaration without
=cannot be used, because it's not completed yet for use in Cpp2, either it's uninitialized or undefined or ...JohelEGP commentedon May 8, 2023
The thing is,
=without:is assignment.If the declaration is followed by an assignment, where's the definition?
msadeqhe commentedon May 8, 2023
The first assignment is the definition:
point: type = { operator=: (out this, x: int, y: int) = {} } main: () = { // Declaration and definition v: point = (0, 0); // Declaration u: point; // Definition // It generates: `u.construct(0, 0);` u = (0, 0); // ERROR! This is not a definition. // BTW it generates wrong Cpp1: `u.value() = 1, 1;` u = (1, 1); }JohelEGP commentedon May 8, 2023
It seems plausible that the definite first assignment is the definition.
But I'm not convinced.
Thinking about definite first assignment made me remember that Cpp2 doesn't have forward declarations.
So perhaps it's not a matter of declaration vs. definition.
In Cpp2, things that can be without an initializer are:
virtual thisfunctions, which are pure virtual.operator==,operator<=>, which are defaulted.forloop's parameterized-statement.Added type and namespace alias declarations, closes hsutter#273
Added type and namespace alias declarations, closes hsutter#273