hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.43k stars 236 forks source link

[SUGGESTION] Documenting `==` as `constexpr` and using unified declaration syntax for aliases #1215

Open vladvrabie opened 1 month ago

vladvrabie commented 1 month ago

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?

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.

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.

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.

jcanizales commented 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.

vladvrabie commented 1 month ago

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.

farmerpiki commented 1 month ago

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"...

DyXel commented 1 month ago

I think its fine to keep the issue all together like this for now.

jcanizales commented 1 month ago

Yeah I was just trying to clarify what was being brought up, not commenting on whether it should be split in two separate threads.

hsutter commented 1 month ago

[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?

filipsajdak commented 1 month ago

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?

hsutter commented 1 month ago

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.

  1. I do think the documentation should do better about explaining that == lowers to Cpp1 constexpr for functions and objects.
  2. 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.