[SUGGESTION] Documenting `==` as `constexpr` and using unified declaration syntax for aliases
TLDR: Implementing function/object aliases as constexpr is confusing/don't work as aliases. == should mean constexpr. Aliases can use the unified declaration syntax name : kind = statement.
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? Not in C++, but in Cpp2 it should simplify the way it is tought and how the user thinks about / uses the syntax.
I've been learning Cpp2 since the documentation website came out and it very much feels like learning a new language. I think the perspective of a 'beginner'/'newcomer' is valuable and can teach us how Cpp2's syntax can be perceived and understood.
For a newcomer, it is difficult to undestand what == does and how it behaves (or why it is inconsistent) when it is tought as creating aliases. Some aliased elements (namespace/type) behave different than others (function/object) because of the chosen implementation (using vs constexpr).
Unified declaration syntax for namespace/type aliases
For me, an alias means "giving a new name to something". It feels more natural to use the unified syntax and I think the syntax for these aliases can be simplified to:
chr : namespace = std::chrono;
imap : <T> type = std::map<i32, T>;
(taken from the documentation website)
This is distinct from declaring namespaces/types (which requires {} after =).
Extra discussion: More or less using?
- More: If the above syntax for aliases is not clear enough, we could make it stand out with something like
using chr : namespace = std::chrono; - Less: We could eliminate the
usingstatements in the syntax to bring names with something along the lines of_ : namespace = std::views;or* : namespace = std::views;or: namespace = std::views;; Credits to @farmerpiki for this
I would personally leave things as they are, no more or no less using.
Function aliases are not... aliases
If I have a function and I want to create an alias to it, as a (hypothetical) newcomer I would think to use ==. The overall aliases section in the documentation even says it lets me "define a synonym for" something.
-
For an alias, using the same function signature seems natural, but this gives the error
'return': cannot convert from 'cpp2::i32 (__cdecl *)(const int)' to 'cpp2::i32'square: (i: i32) -> i32 = i * i; square_alias: (i: i32) -> i32 == square; -
To solve the error, try auto return type. This compiles and runs, but
square_aliasis not a true alias, it returns the address ofsquaresquare: (i: i32) -> i32 = i * i; square_alias: (i: i32) -> _ == square; -
Deduced type. It compiles and runs,
square_aliasis a true alias, but this is technically not a function alias anymore, but an object aliassquare: (i: i32) -> i32 = i * i; square_alias: _ == square;
These examples show that the "function alias syntax" does not fulfill the user's intent of defining an alias like in the namespace/type case.
I think the problem lies in the fact that these function aliases are implemented as new constexpr functions. And when we write code like square: (i: i32) -> _ == i * i;, we are more interested in the constexpr part, not on the alias part.
Once we free the constexpr functions from the shackles of alias and users are tought that == means constexpr, it actually makes the syntax baggage lighter and enables the user to realize that == can be used inside of classes to create constexpr constructors and methods (which is also not documented anywhere).
And there are already other concepts in Cpp2 to deal with function aliases, such as std::function or function pointers.
Object aliases are also not aliases*
*Except when they are function pointers
Let us consider the case of object aliases now, just as above.
-
For an alias, using the same type seems natural, but this gives the error
expression did not evaluate to a constantval: i64 = 1024; val_alias: i64 == val; -
This would run if val were initialized on the same line as its declaration. But it doesn't work because it is initialized after
val_alias.val: const i64 = 1024; val_alias: i64 == val; -
This prints 2 different addresses, which means
val_aliasis not really an alias, but a copy, which is not what was expected/desired.val: std::array<int, 3> == (1, 2, 3); val_alias: _ == val; main: () = { std::cout << val[0]& << "\n"; std::cout << val_alias[0]& << "\n"; }
Same point as above: Using == for aliases does not create aliases (new names for existing objects), but actually allocates new constexpr objects.
What is the most natural way to make object aliases in Cpp1? References! But Cpp2 doesn't have that (...yet?). Other alternatives would be to use pointers or std::reference_wrapper.
This is two orthogonal issues in one, right? The fact that currently aliases aren't real aliases, and a proposed new syntax for type and namespace aliases.
What started me thinking on this was Herb's Autumn update, section "Generalized aliases+constexpr with ==".
Because the two are put under the same syntax, I thought I should highlight the problem with (some) aliases and propose a solution (well, 2 solutions). I suggested the new syntax here because I wrote
For a newcomer, it is difficult to undestand what == does and how it behaves (or why it is inconsistent) when it is tought as creating aliases.
and I thought a proposal to separate the 2 features is beneficial and supports my argument.
Let me know if it's better to separate the issues. I can move the syntax proposal to a new issue.
I also kinda think it should be 2 issues, also the constexpr fact about == maybe should be emphasised more on the documentation side... when @vladvrabie mentioned this initially I was seriously confused, also was confused when I saw the generated code for my "alias"...
I think its fine to keep the issue all together like this for now.
Yeah I was just trying to clarify what was being brought up, not commenting on whether it should be split in two separate threads.
[Updated]
Thanks! I'm thinking about your examples, but I have a question...
I think you're saying you're surprised this doesn't work:
func: (i: i32) -> i32 = i * i;
func_alias: (i: i32) -> i32 == func; // doesn't work
But I wouldn't expect it to work unless you include the argument list (note this doesn't work today):
func_alias: (i: i32) -> i32 == func(i); // could be ok, requires argument list
To me this feels consistent with what we have today for type aliases:
map: <T,U> type = { /*... like std::map ...*/ };
map_alias: <T> type == map; // doesn't work
map_alias: <T> type == map<i32, T>; // ok, requires argument list
Does that make sense, that aliases still need argument lists?
I realize that doesn't fully address your question about identity. It does make me think that function aliases should additionally be lowered as always inline perhaps?
Arguments are needed. Based your map example, it should be possible to do something like this:
func: (i: i32, j:i32) -> i32 = i * j;
func_alias: (j: i32) -> i32 == func(42, j);
I think this is similar to calling a function within lambda function in c++:
auto func_alias = [](int j) -> int {
return func(42, j);
}
right?
It's true that there are two related concepts here, "synonym" and "identity"... currently:
- Type/function/object aliases are all synonyms (they always have the same meaning as the rhs, as if a use was replaced by the rhs).
- A type alias is also an identity (it resolves to exactly the same type) which the other two do not (they don't resolve to the same function/object address).
The way to get function/object identity is to use a pointer (for non-templates). This is one reason I originally thought of making type aliases use pointer syntax, which would make it explicit that the code is talking about a pointer to a type. But I didn't explore that very far because then we'd have to distinguish a pointer to a type (a new compile-time concept) vs a pointer to an object of that type (ordinary pointer), which is very subtle, so I didn't pursue that.
- I do think the documentation should do better about explaining that
==lowers to Cpp1constexprfor functions and objects. - Would the rest of the confusion be helped by using a word other than "alias" to denote that the declared thing is always a synonym for the rhs? Perhaps even rename "alias" to "synonym" to reduce the identity connotations?
I do think that so far it's been working well to use a single concept and syntax for type/function/object synonyms, names that are always the same as the rhs. But I could be wrong and I do appreciate the usability feedback from someone new to the syntax, and I'll keep thinking about it.