-
Notifications
You must be signed in to change notification settings - Fork 249
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] Confusing std::vector ctor calls in Cpp1 and Cpp2 #193
Comments
Perhaps another way to state this issue is: Should cpp2 do fine-grained removal/repair of unfortunate cpp1 API choices? |
What about if we had a literal for the size, like a: std::vector<int> = (5, 1_s); // a vector of 5 ones
b: std::vector<int> = (5, 1); // a vector of 5 and 1 and then we could do it like this: b := std::vector<int>(5, 1_s); // vector of five ones |
How would you combine that with existing suffixes like |
Soon you will be able to write: a: std::vector<int> = (5, 1 as std::size_t); |
@filipsajdak Yes, thats good. Would an overload for a |
I think you confused me with @filipsajdak |
What about auto a = vector<std::size_t>{5z, 1z}; // vector of five and one
auto b = vector<std::size_t>(5z, 1z); // vector of five ones |
@JohelEGP cheater. 🙂 I agree that there is an issue. I am just trying to advertise the cpp2 casting feature. What we want to achieve is unambiguous syntax. The spotted issue sure will need some reasonable solution. |
IMO this would be fairly difficult to solve while keeping full compatibility with Cpp1, the issue is that in C++, the aggregate (i.e. So, this would either require a change in constructors of |
Honestly, it would be easier if C++ just left |
This would be really nice. An alternative idea could be to use an explicit cast for initializer lists.
|
@marioarbras Or we could have it the other way round, depending on which is more common: initializing a vector with a a: std::vector<int> = (5, 1); // vector of five and one or initializing it with a certain amount of elements like: b := std::vector<int>(5, 1); // vector of five ones Which would be the more common usage? Personally I use the initializer list more. |
Yes, fixing that problem is on my list to address as follows (I just haven't implemented it yet):
The idea is that the parens in |
Honestly, to me that looks just slightly better than manual insertion 😅 I suppose we could just explicitly use braces for initializer lists only, where if Then again, this would fall flat for aggregate types such as |
What about this situation though:
Is v2 a vector of 1 element x+1, or is it a vector of x+1 zeros, where the size was just enclosed in redundant parentheses? |
This could be an opportunity to add a small language feature. In cases where you want multiple parameter packs, it would be nice to have a symbol to disambiguate/delineate where you the caller want one to end and the next to begin, this could also be useful in this case.
I'm not sure what that would look like, but may be worth considering
On 2 January 2023 17:58:46 SwitchBlade ***@***.***> wrote:
IMO this would be fairly difficult to solve while keeping full compatibility with Cpp1, the issue is that in C++, the aggregate (i.e. {}) constructor favors initializer lists, while the "classic" parentheses never generate initializer lists.
So, this would either require a change in constructors of std::vector (and other containers too) to outright remove the size & value constructor, or use some kind of tag like with std::in_place_t to disambiguate overloads; or to forego the initializer-list/aggregate constructor form in Cpp2, which would prevent the ambiguity, but also disable initializer lists.
—
Reply to this email directly, view it on GitHub<#193 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQL2HR2UVM52N2USNG3WQMJNHANCNFSM6AAAAAATO3APAE>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
This makes a lot of sense since you can already do this:
which transpiles to auto il = { 1, 1 }; // same as auto il = std::initializer_list{ 1, 1 } |
Correct me if I'm wrong, but isn't |
The extra parens would be meaningful, as they're saying that "this argument list contains a single element, which is a list." So:
What do you think, does that sound reasonable? |
That does sound reasonable, although to me it still seems like a potentially error-prone thing to use double parentheses for lists, since the majority of people are used to Also gives me a bit of a tingle with templates, I cannot come up with a reasonable example off the top of my head right now but it could cause some unexpected pitfalls in complex template pack expansion (i.e. pack expansion wants to use parentheses for grouping, but because the pack has only 1 element it gets treated as a list). |
Since functions are declared like mappings between tuples, Personally, I'd find the extra parens more confusing than the fact that a constructor-call is not necessarily the same as an assignment, so while I agree that the cpp1 example given in the original post has potential for confusion, I don't agree that the cpp2 example does. I see more risk for confusion with v1: vector<int> = (x + 1);
v2: vector<int> = ((x + 1)); since people are probably used to parentheses being only about association/ordering. |
I agree. Personally, at least, I have always thought (and felt, maybe?) of parentheses as math-related syntax, with the meaning of expression ordering/grouping. And I would assume that most people think of parentheses as something you use to disambiguate What also can make it more confusing is the possibility of whitespace in between double parentheses. If The way I see it is that aggregate/list initialization needs to be dealt with by separating construction from lists, rather than replacing the current status-quo with a different syntax. |
@HerbSutter is the plan to reserve curlies {} for scopes only in cpp2?
On 2 January 2023 19:46:27 "G. P. Müller" ***@***.***> wrote:
Since functions are declared like mappings between tuples, (...) -> (...), I would find it consistent that assigning such a tuple to a vector, v: vector<int> = (5, 5) would produce a vector of two elements.
Not sure whether there's other parts of the language syntax that could be considered in this consistency argument.
Personally, I'd find the extra parens more confusing than the fact that a constructor-call is not necessarily the same as an assignment, so while I agree that the cpp1 example given in the original post has potential for confusion, I don't agree that the cpp2 example does.
I see more risk for confusion with
v1: vector<int> = (x + 1);
v2: vector<int> = ((x + 1));
since people are probably used to parentheses being only about association/ordering.
—
Reply to this email directly, view it on GitHub<#193 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQP2PRAZ4257CEOIIXTWQMWBBANCNFSM6AAAAAATO3APAE>.
You are receiving this because you commented.Message ID: ***@***.***>
|
My 3 cents on the issue:
|
I think it is still confusing. What about named constructors? |
I could be wrong, but I think an initializer list is implicitly constructed when you do |
The above concept code is for illustrative purpose only, to highlight 4 different approaches to the subject.
Sorted preference max->min: v2, v3, v4, v1. In nested containers the (()) syntax becomes cumbersome and 4 )))) in a row reminds me of Lisp. |
I agree with the v2 > v3 > v4 > v1 preference, although to me Then again, v1 is just way too many parentheses. |
IIRC, in Cpp2, |
Per @wolfseifert 's experiment, the current status quo would have to be changed no matter what: main: () -> int = {
a: std::vector<int> = (5, 1); // vector of five and one
b := std::vector<int>(5, 1); // vector of five ones
} currently transpiles to: std::vector<int> a { 5, 1 }; // vector of five and one
auto b { std::vector<int>(5, 1) }; // vector of five ones (Confusing!) For Option 0, the second case would need to be changed to produce: auto b { std::vector<int>{5, 1} }; |
Related consideration: Do you already have a plan in mind for the syntax of designated initializers, @hsutter ? That might be what "forces" CPP2 to use |
@SebastianTroy hmmm.. that tastes very... python-y But to me the brackets proposal seems to hit the same issue as the double-parentheses one, in that the brackets are commonly associated with indexing, and using them for list initialization could create confusion. |
I dont think there is any overlap, no, since scopes do not appear anywhere a prvalue might. Also, using braces for lists will have a good mental parallel to scopes: a scope is a collection of statements, while a list/aggregate is a collection of expressions. |
I've read this comment from an issue in Carbon about grammar issues of |
|
Thanks, everyone, for all the comments.
Let's add that option to the list, and I've now implemented (3) too to try it out: Option 3: Use
|
…thread for #193 This commit paves the way for in the future (not now) possibly experimenting with allowing expression-list to have braces, if it's important to provide explicit initializer-list expression syntax (mostly for function arguments)
So the only way to pass a a: std::vector = (5, 1); print(a); // prints 5 1 Neither print(std::vector(5, 1)); // prints 1 1 1 1 1 nor print(: std::vector = (5, 1)); // does not transpile do the trick. Or did I miss something? |
@wolfseifert does |
No, it does not. I will open a new issue for this later. |
…comment thread Sample test case: ``` print: (x:_) = { for x do :(elem:_) = { std::cout << elem << " "; } } main: ()->int = { print( : std::vector = (5,1); ); } ```
I've been meaning to allow unnamed objects eventually, and seeing you actually trying to write the code pushed me over the edge to implement it now. :) This now works:
|
Is it possible to infer type from function declaration?
... and from variables declaration:
|
How I understand this so far:
|
What do you think about that: print( (5,1) as std::vector ); // for generic functions; prints "5 1"
taking_vector( forward (5,1) ); // assuming that this function expects a vector and
// that we can deduce the type from the function declaration For the main: () -> int = {
print( std::vector<int>().create(5, 1) ); // prints 1 1 1 1 1
// or ...
print( create<std::vector>(5, 1) ); // prints 1 1 1 1 1
}
template <typename X>
auto create(std::vector<X>&&, std::size_t size, X&& value = X{}) -> decltype(auto)
{
return std::vector<X>(size, value);
}
template <template <typename...> class V, typename X>
auto create(std::size_t size, X&& value = X{}) -> decltype(auto)
{
return V<X>(size, value);
}
print: (x:_) = {
for x do :(elem :_) = {
std::cout << elem << " ";
}
std::cout << std::endl;
}
#include <vector> Names can be adjusted ( |
I have just realized that syntax for unnamed objects : std::vector = (5, 1) Follows the same syntax as unnamed functions (aka lambdas): : (i:int) -> int = { return i+1; } |
Re syntax: Yes, in both cases it's the identical syntax as a named object/function, but without the name. I think we've bottomed out on this for now. Thanks, everyone! |
…thread for hsutter#193 This commit paves the way for in the future (not now) possibly experimenting with allowing expression-list to have braces, if it's important to provide explicit initializer-list expression syntax (mostly for function arguments)
…er#193 comment thread Sample test case: ``` print: (x:_) = { for x do :(elem:_) = { std::cout << elem << " "; } } main: ()->int = { print( : std::vector = (5,1); ); } ```
#284 (comment) means it's possible to call
|
That's a nice hack. |
In Cpp1 we have the confusing fact that
Experimenting with (pure) Cpp2 I got a similar case
This becomes clear when looking at the tanspiled code
For me the Cpp2 case looks even more confusing (than the Cpp1 case), because both ctor calls use parenthesis.
The text was updated successfully, but these errors were encountered: