-
Notifications
You must be signed in to change notification settings - Fork 251
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SUGGESTION] Typed Expressions; generalized constructors, UDLs, unnamed variables and functions #463
Comments
Also this suggestion won't add any new syntax to the language, it uses the existing syntax |
In a nutshell:
|
I like the other suggestion better I think. Would this be context-free?
…On Sun, 21 May 2023, 17:50 Sadeq, ***@***.***> wrote:
In a nutshell:
- ID : TYPE would be a declaration if it's at the start of a
statement.
- We use ID : TYPE to specify the type of a declaration.
- EXPR : TYPE would be a typed expression if it's not at the start of
a statement.
- We use EXPR : TYPE to specify the type of an expression.
- : TYPE = SOMETHING would be an unnamed variable.
—
Reply to this email directly, view it on GitHub
<#463 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/A2KJHTTIO3C2EI2JIBJYF7LXHICATANCNFSM6AAAAAAYJLD2PA>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Thanks. Yes, It would be context-free, because the behaviour of // It's a declaration.
something: Type;
// It's a typed expression.
call(something: Type);
// It's a parameter declaration.
call: (something: Type) = {} |
Briefly: // `A` is a declaration.
A: Type = 0;
// `B` is a declaration.
// `X` is a typed expression.
B: Type = X: Type; |
A typed expression can subsume a UDL better than UFCS. Let's consider the type
As you can glean from the comments, Just switch from having a
For an alias template (e.g., to replace the UDLs |
From our experience at https://github.com/mpusz/units, which has quantity references (e.g.,
As seen from "Unit-specific Aliases", a typed expression isn't thus affected. See also UDLs vs Quantity References for many major pain points against UDLs.
|
I expanded on the quote above. Allow me to expand the summary's table:
As you you can see, a typed expression is the best in almost all aspects. |
@mpusz You may be interested in looking at this. In particular, the 3 comments above. |
@JohelEGP Thanks for explaining about indirect construction of UDL and UFCS, I wasn't aware of it.
I changed the example to have typed expressions: // simple numeric operations
static_assert(10:km / 2 == 5:km);
// unit conversions
static_assert(1:h == 3'600:s);
static_assert(1:km + 1:m == 1'001:m);
_s: = 1:s;
kmph:type == decltype(1:km / _s);
// dimension conversions
static_assert(1:km / 1:s == 1'000:m / _s);
static_assert(2:kmph * 2:h == 4:km);
static_assert(2:km / 2:kmph == 1:h);
static_assert(2:m * 3:m == 6:m2);
static_assert(10:km / 5:km == 2);
static_assert(1'000 / 1:s == 1:kHz); For this to work, I think that unit types may have an extra template parameter to indicate the prefix. For example |
Don't worry. The library has taken care of all that. |
From commit 0982b8e:
That works well for named declarations. Commit 1090a31 also enabled
|
Good point. So it would be like to pronounce:
In general:
|
Assignment to Typed Expression: // `A` is a declaration.
A: Type = 0;
// `B` is a declaration.
// `A` is a typed expression.
B: Type = A: Type;
// It's equal to:
// = Type::operator=(out this, B).operator=(something)
C: Type = B: Type = something;
// (2 + 2): Type = something;
// x++*.f(): Type = something;
// It's equal to:
// = Type::operator=(out this, something)
D: Type = : Type = something; It's can be safe to disallow assignment in case 3, because it's rvalue: // ERROR `B: Type` is rvalue.
C: Type = B: Type = something; I'm going to categorize them. They all have a similar syntax but semantically they are different in this way:
Unnamed Variables are a special Typed Expression.So we can think about it that Unnamed Variables are a special Typed Expression. This is a generalized syntax for both of them: (something: Type = value) Unnamed Variables don't have the After that, they would be categorized in this way:
This categorization will reduce concept count. |
I'm trying to find a general rule to reduce concept count. Also Unnamed Functions could be somehow a special Typed Expression if Cpp2 would support issue suggestion #391 titled "Statement-expressions, (something: (args) -> Type = { /*statements*/ }) Unnamed Functions don't need the After that, they would be categorized in this way:
I have to clarify about the syntax (described above) of typed expressions:
So this example won't be allowed: // WRONG! This typed expression applied to a statement block.
{ /*statements*/ }:(args) -> Type |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
Yes, you're right. I will correct this paragraph from my comment.
EditThanks @JohelEGP I've removed that misleading information from my comment. |
I gave up on this idea. So unnamed functions shouldn't be immediately called in this way, because it's inconsistence with how unnamed variables work. |
@JohelEGP you provided "Yes" in the table for "Easy composition for derived units". How it is possible with typed expressions? |
As a replacement to UDLs, |
Easy composition for derived units and quantities does not mean that you can |
I understand. |
Somehow if multiplication of units could be modeled as template template parameters, we would have: // Type aliases
kg: <T> type == com<kg_type, T>;
m2: <T> type == com<m2_type, T>;
// 10:kg:m2 is com<m2_type, com<kg_type, int>>
a: = 10:kg:m2; But there isn't any notation better than 1:N:m == 1:kg:m2 / 1:s2
1:J / 1:mol:K == 1:m2:kg / 1:s2:mol:K |
I do not think V2 makes it redundant. The V2 provides a solution that gathers the best features from all the options we had before. In V2 we have units that "have only to be defined for named units", and the "unnamed" derived units are obtained by applying unit equations on the predefined ones. In V2 user never types Unit-specific alias in V1 are pointing to quantity types rather than units so we can't obtain derived unit or |
It doesn't seem possible for that, |
What if its type is: m: == Comp<m_type, T>;
N: == Comp<N_type, T>;
1:N:m is Comp<m_type, Comp<N_type, int>>
1:N:m:kg is Comp<kg_type, Comp<m_type, Comp<N_type, int>>> And |
Sorry, I was too brief in my reply. That certainly works. One of the points of
The nesting required to make this work is suboptimal. I tried to make it work without disrupting the design. |
Another point against alias chaining is the extra construction per type. I have to say that the readability and composability of your example is superb:
|
This still stands. Here are some examples of chained typed expressions that work well from #284:
It's unfortunate that to make chaining work for units I've left the table of #463 (comment) untouched, I'm thinking that rather than aliasing the existing class template Here's my attempt: |
|
Additionally to use variable or function names within a: Type = 0;
2:<decltype(a)*int> == 2:decltype(2:decltype(a) * 2:int) Unary operators and other combinations are possible, but we use 2:<<Abc + Xyz>++ * <Abc + Xyz>++> == 2:decltype((2:Abc + 2:Xyz)++ * (2:Abc + 2:Xyz)++)
2:<Abc < Xyz> == 2:decltype(2:Abc < 2:Xyz)
2:<Abc > Xyz> == 2:decltype(2:Abc > 2:Xyz)
|
This does not work in a generic sense. Even though it is perfectly fine for Probably you mean something like:
which may work. |
Thanks. Yes I mean that. Infact I was thinking about allowing unnamed uninitialized variables within // `:N` and `:m` are unnamed uninitialized variables.
2:<N*m> == 2:decltype(:N * :m)
|
Also it's syntactically possible to use a: = 120:A;
// `(A)` is not a function signature, because it's a typed expression.
a: = 120:(A);
// But `(A)` is a function signature, because it's a declaration.
a: (A) = ... Examples: 2:int == 2:(int)
2:(N*m) == 2:(kg*m2/s2)
1:(J/mol/K) == 1:(m2*kg/s2/mol/K)
1:(J/(mol*K)) == 1:(m2*kg/(s2*mol*K))
a: Type = 0;
2:(decltype(a)*int)
2:((Abc + Xyz)++ * (Abc + Xyz)++)
2:(Abc < Xyz)
2:(Abc > Xyz) Although |
That certainly works in favor of unit libraries. It can also work for C++ standard library range piping when the pipes don't have input:
Of course, the standard syntax
I was going to suggest that for the inner
That'd be a good shorthand for decltype(std::declval<decltype(2:N)>() * std::declval<decltype(2:m)>()) |
You're right, To have general use case, it seems // When there is one <>, it's for type composition.
variable1: <A*B++> = /*expression*/;
// That's because the following is already an error in Cpp2 if `T` is a template parameter:
// ERROR! `T` is not a declared type! Also `T` cannot be a template parameter.
variable2: <T> = /*expression*/;
// Instead, it has to be declared like the following (already works):
variable3: <T> T = /*expression*/;
// When there is two <>, always:
// - The first <> is for template parameters.
// - The second <> is for type composition.
variable4: <T> <A*B/T> = /*expression*/;
// The <> before () is always for template parameters.
function1: <T> (a: <A*T>, b: <B*T>) -> <A*B> = { /*statements*/ }
// OK: The type of template paramteter `v` is `<A*B>`.
function2: <v: <A*B>> () = { /*statements*/ }
// OK: The type of template paramteter `v` is template parameter `std::vector<T>`.
function3: <v: <T> std::vector<T>> () = { /*statements*/ } So function1: (a: A, b: B) -> decltype(a*b) = { /*statements*/ }
function2: (a: A, b: B) -> <A*B> = { /*statements*/ }
function3: (a: A, b: B) -> <A*decltype(b)> = { /*statements*/ } Also Cpp2 can go furthur and change the name of function1: (a: A, b: B) -> type(a*b) = { /*statements*/ }
function2: (a: A, b: B) -> <A*type(b)> = { /*statements*/ }
a: A = ();
variable1: type(a) = /*expression*/; In this way |
Briefly it would mean: a: = ... // It's a variable or function object. It depends on the right hand side of assignment.
b: A = ... // It's a variable.
c: <A> = ... // It's a variable. <A> is a composed type. Currently it's an error in Cpp2.
d: <T> T = ... // It's a variable template.
e: <T> <T> = ... // It's a variable template. The second <T> is a composed type.
f: <T> type(expr) = ... // It's a variable template. `type` is `decltype` here.
g: type(expr) = ... // It's a variable. `type` is `decltype` here.
h: type = ... // It's a type.
i: <T> type = ... // It's a type template.
j: <T> (args...) = ... // It's a function template. The return type is `void`.
k: <T> (args...) -> T = ... // It's a function template.
l: <T> (args...) -> <T> = ... // It's a function template. The return type <T> is a composed type.
m: <T> (args...) -> type(expr) = ... // It's a function template. `type` is `decltype` here.
In general: // <A> is a composed type.
n: <A> = ...
// <T> is a template parameter.
// `something` can be either a type, composed type, function type, `type` or `namespace`.
o: <T> something = ... // `o` is a template.
// `something` can be either a type, composed type, function type, `type` or `namespace`.
p: something = ... // `p` is not a template. |
C23 got |
|
I examined
So type composition within declarations would be like this: A: type = { /*declarations*/ }
B: type = { /*declarations*/ }
function: <T> (a: A*T, b: B*T) -> A*B = { /*statements*/ }
X: type = { /*declarations*/ }
variable: <T> X*T = /*value*/; That's because Briefly, it means that:
|
That's interesting. Also C23 has
I like how Cpp2 already has |
I agree that's not intuitive from human-language point too. In cpp2 |
|
Yet Another Alternative SolutionWhat if first we try to fix Abc: type = { /*declarations*/ }
x1: = 1.Abc; // this suggestion
x2: = 1.Abc(); // in current Cpp2
y1: = (1, 2).Abc; // this suggestion
y2: = (1).Abc(2); // in current Cpp2
z1: = (1, 2, 3).Abc; // this suggestion
z2: = (1).Abc(2, 3); // in current Cpp2 So
Using 10.(N*m) == 10 * 1.kg * 1.m2 / 1.s2
// <> can be used instead of ()
10.<N*m> == 10 * 1.kg * 1.m2 / 1.s2 But this alternative solution has a problem (opinion-based). The problem is that if |
Now, if we use // Type composition within declarations:
A: type = { /*statements*/ }
B: type = { /*statements*/ }
function: <T> (a: A*T, b: B*T) -> A*B = { /*statements*/ }
point: type = {
operator=: (out this, a: A, b: B) = { /*statements*/ }
}
main: () = {
a: = ().A; // It calls the default constructor.
b: = 12.B; // It calls the constructor `operator=(out this, 12)`.
c: = 12.int.B; // Also they can be chained.
// Type composition within typed expressions:
x: = 10.(N*m) == 10 * 1.kg * 1.m2 / 1.s2;
y: = 10.(N*m) == 10.(kg*m2/s2);
z: = (a, b).point;
} |
I don't think that's context-free:
Even with parentheses, |
Yes, it's not context-free, because it's based on UFCS The motivation of this alternative suggestion is that if UFCS |
They are not so comparable. |
That's right but I've to clarify I don't want to say that |
From this comment:
So simply |
The type of |
Surely the same suffix would result in the same type anyway?
On 15 June 2023 13:34:39 Johel Ernesto Guerrero Peña ***@***.***> wrote:
The type of 1s + 1.0s is their common type, which is the type of 1.0s.
—
Reply to this email directly, view it on GitHub<#463 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQJWGJE4E3JRTBB5PYTXLL6NVANCNFSM6AAAAAAYJLD2PA>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
What if Cpp2 uses the following syntax for named arguments? x: = call(name: = "someone", age: int = 20); It may conflict with this suggestion ( |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
This is an alternative solution to this issue. If you don't like
...TYPE
notation for object construction because of its UDL syntax, this is an alternative solution. In this suggestion, objection construction would use familiar notationEXPR:TYPE
(aka Typed Expression) which is similar to how Cpp2 programmers use it within declarations.Consider
EXPR:TYPE
as syntactic sugar to(: TYPE = EXPR)
. For example:Literally if
x:int
is at the start of a statement or function parameter, it would be a declaration, otherwise it would be a Typed Expression.EXPR:TYPE
is similar to...TYPE
suggestion, except with the following advantages:...TYPE
would complicate the grammar especially for working within function chaining.a++:Abc
). It depends on operator precedence, and it's left to right.And this notation has the following disadvantages:
:
. BTW it's opinion based.I have to explain
:
withinSOMETHING:TYPE
is for object construction (as an expression) or declaration (as a statement), but::
withinSOMETHING::TYPE
is scope resulotion operator for qualified names. They can be combined like10++ : my::Type
. Also after the object is constructored, we can use operator dot oroperator()
oroperator[]
or ... to access members from it, e.g.10:Type.call()
or10:Type[0]
.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.
EXPR:TYPE
, parentheses are not necessary whenEXPR
has operators with higher precedence.():TYPE
, it calls the default constructor(args...):TYPE
FUNCTION()
, it calls a function without argumentsFUNCTION(args...)
obj.FUNCTION()
obj.FUNCTION(args...)
using
statement before they can be applied to literals._
, thus they will be distinguished from UDLs which are declared in the Cpp1 standard library.TYPE(args)
it doesn't work with UFCS intentionally. UFCS should not work on constructors as described in this comment. Compare:Describe alternatives you've considered.
These are alternative solutions:
Thanks.
The text was updated successfully, but these errors were encountered: