Open vladvrabie opened 1 month ago
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:
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.
==
lowers to Cpp1 constexpr
for functions and objects.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.
TLDR: Implementing function/object aliases as
constexpr
is confusing/don't work as aliases.==
should meanconstexpr
. Aliases can use the unified declaration syntaxname : 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
vsconstexpr
).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:
(taken from the documentation website)
This is distinct from declaring namespaces/types (which requires
{}
after=
).Extra discussion: More or less
using
?using chr : namespace = std::chrono;
using
statements 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 thisI 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'
To solve the error, try auto return type. This compiles and runs, but
square_alias
is not a true alias, it returns the address ofsquare
Deduced type. It compiles and runs,
square_alias
is a true alias, but this is technically not a function alias anymore, but an object aliasThese 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 createconstexpr
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 constant
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
.This prints 2 different addresses, which means
val_alias
is not really an alias, but a copy, which is not what was expected/desired.Same point as above: Using
==
for aliases does not create aliases (new names for existing objects), but actually allocates newconstexpr
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
.