[SUGGESTION] Array Literals with the support of both Multidimensional and Jagged Arrays #637
Replies: 37 comments 28 replies
-
Herb points out that
These arrays would effectively translate to std::array in cpp1 or some cpp2::array if Herb wants a different type.
There won't be any initialiser lists in cpp2, they'll be just an implementation detail on cpp2 side. |
Beta Was this translation helpful? Give feedback.
-
So "hard to parse" is the only complaint against x0: = identifier[1]; // [1] is a subscript operator.
x1: = [1]; // [1] is an array literal. It's like parenthesis in Cpp2 and Cpp1: x0: = identifier(1); // (1) is a function call operator.
x1: = (1); // (1) is an expression group. The only difference is that |
Beta Was this translation helpful? Give feedback.
-
It seems Herb's answer was to this comment:
I have to clarify that I don't suggest to also use |
Beta Was this translation helpful? Give feedback.
-
I think it is important for initializer list/ array-type constructors to have a different symbol/syntax from everything else, as they are a special case. We also need avoid the double paren |
Beta Was this translation helpful? Give feedback.
-
I think initialiser list contructors should be viewed as simple contructors where we pass arrays as parameters, they should not be a special case. so if
should translate to initialiser list syntax instead of
|
Beta Was this translation helpful? Give feedback.
-
The issue with that is that for the case of a 2D vectorst the ('s and [' become too many The |
Beta Was this translation helpful? Give feedback.
-
That's not what I recommend. The syntax would be
Like I said, just view it as passing an array to a constructor. Here we are passing the array
But I think this should be allowed if the constructor is marked implicit, what do you guys think? |
Beta Was this translation helpful? Give feedback.
-
Parenthesis around #include <iostream>
taip: type = {
operator=: (out this, i: int) = {
std::cout << i << "\n";
}
}
main: () = {
t0: taip = 1;
// Parenthesis are not necessary.
t1: taip = (1);
} So #include <string>
#include <iostream>
taip: type = {
operator=: (out this, text: std::string) = {
std::cout << "OK. That's a string literal.\n";
}
operator=: (out this, list: std::initializer_list<int>) = {
std::cout << "OK. That's an array literal.\n";
}
}
main: () = {
s0: taip = "text";
t0: taip = [1, 2, 3];
// Parenthesis are not necessary.
s1: taip = ("text");
t1: taip = ([1, 2, 3]);
} |
Beta Was this translation helpful? Give feedback.
-
If I understand correctly, the inner vectors will only need |
Beta Was this translation helpful? Give feedback.
-
I thought constructors were supposed to be explicit? you guys can ignore my comments cuz it seems my understanding was wrong |
Beta Was this translation helpful? Give feedback.
-
1. Multidimensional ArraysI have to clarify that currently I don't suggest multidimensional arrays, but they are a possible feature to explore. Multiple syntax choices are available for multidimensional arrays: a: = [((1, 2), (3, 4)), ((5, 6), (7, 8))];
b: = [1, 2; 3, 4;; 5, 6; 7, 8];
c: = [((1, 2); (3, 4);); ((5, 6); (7, 8););];
d: = [((1; 2); (3; 4)); ((5; 6); (7; 8))];
e: = ... Here, I'm explaining the first one of those possible syntax for multidimensional arrays: a: = [((1, 2), (3, 4)), ((5, 6), (7, 8))]; This syntax requires loosely multidimensional arrays, when the type of array literal is not specified within declaration (e.g. the type of variable 1.1. Loosely Multidimensional Arrays1-dimensional arrays are loosely multidimensional arrays. It's useful in generic programming. That's because there is two types of dimensions within arrays:
// These parenthesis don't contain commas:
[(0), (1), (2), (3)] // OK. It's equal to [0, 1, 2, 3]
[0, (1), ((2)), (((3)))] // OK. It's equal to [0, 1, 2, 3]
// These parenthesis contain commas:
[(0, 1), 2, 3] // ERROR! They must have identical commas in all corresponding elements.
// This is a combination:
[(0, (1)), (2, (3))] // OK. It's equal to [(0, 1), (2, 3)]
list: /*something*/ = [1, 2, 3];
// Only `1` is required dimension. These zero-value indexes are optional dimensions.
s0: = list[1] == 2; // true
s1: = list[1, 0] == 2; // true
s2: = list[1, 0, 0] == 2; // true The opposite is not true. Multidimensional arrays cannot be treated as 1-dimensional arrays. I have to explain that these rules help us to get rid of parenthesis when they are used to only group expressions inside arrays without any intention to add another dimension: // It's a 1-dimensional array.
x: = [(1 + 2), (3 + 4)];
// But optionally it can be used as a two-dimensional array.
a: = x[1]; // OK. It's equal to (3 + 4)
b: = x[1, 0]; // OK. It's equal to (3 + 4)
// It's a two-dimensional array.
y: = [(1, 2)];
// But it cannot be used as a 1-dimensional array.
a: = x[0]; // ERROR! The second index is required.
b: = x[0, 1]; // OK. It's equal to `2` 1.2. More Examplespoint: type = {
operator=: (out this, x: int, y: int) = {}
}
zero_point: () -> point = {
return (0, 0);
}
zero_array: () -> std::initializer_list<int> = {
return [0, 0, 0/*, ...*/];
}
a0: = []; // An empty array
a1: = [ [] ]; // An empty array in array
a2: = [1, 2, 3]; // A 1-dimensional array
a3: = [ [1, 2, 3] ]; // A 1-dimensional array of 1-dimensional arrays
a4: = [(1, 2, 3)]; // A two-dimensional array
a5: = [ [ [1, 2, 3] ] ]; // A 1-dimensional array of 1-dimensional arrays of 1-dimensional arrays
a6: = [((1, 2, 3))]; // A three-dimensional array
a7: = [ [(1, 2, 3)] ]; // A 1-dimensional array of two-dimensional arrays
a8: = [( [1, 2, 3] )]; // A 1-dimensional array of 1-dimensional arrays Although a8: = [( [1, 2, 3] )]; // A 1-dimensional array of 1-dimensional arrays
// It can be visually seen as a two-dimensional array of 1-dimensional arrays
x0: = a8[0][1]; // OK. A 1-dimensional array of 1-dimensional arrays
x1: = a8[0, 0][1]; // OK. A two-dimensional array of 1-dimensional arrays |
Beta Was this translation helpful? Give feedback.
-
2. DictionariesI have to clarify that currently I don't suggest dictionaries, but they are a possible feature to explore. No additional syntax is required for dictionaries, if Cpp2 could support type inference for unnamed variable declarations. 2.2. Dictionaries are arrays of objects.With the help of unnamed variable declarations, we can write arrays of objects: x0: = [: std::pair<std::string, int> = ("a", 1),
: std::pair<std::string, int> = ("b", 2),
: std::pair<std::string, int> = ("c", 3)]; But if Cpp2 could support type inference for unnamed variable declarations, it would be so much simpler in a way that a distinct syntax would be not needed for dictionaries: x0: std::vector<std::pair<std::string, int>> = [: = ("a", 1),: = ("b", 2),: = ("c", 3)];
// It's a dictionary.
x1: = [: = ("a", 1),: = ("b", 2),: = ("c", 3)]; Please, see the next comment for construction with 2.2. Do "arrays of objects" conflict with "multidimensional arrays"?No. There isn't any conflict, because parenthesis have loosely semantic unlike // A multidimensional array of numbers
x0: matrix<int, 2, 3> = [(1, 2, 3), (4, 5, 6)];
// A multidimensional array of objects
x1: matrix<std::pair<std::string, int>, 2, 3> = [
(: = ("a", 1),: = ("b", 2),: = ("c", 3)),
(: = ("d", 4),: = ("e", 5),: = ("f", 6))
];
r1: = x1[0, 1]; // It is `: = ("b", 2)`. In a nutshell, we use But in this case, the type of array literal is known for variable // A multidimensional array of objects
x1: matrix<std::pair<std::string, int>, 2, 3> = [
(("a", 1), ("b", 2), ("c", 3)),
(("d", 4), ("e", 5), ("f", 6))
]; Please, see the next comment for explanation. |
Beta Was this translation helpful? Give feedback.
-
3. Array Literal of ConstructorsIf the type of array literals are specified within declarations, the object construction is much easier and readable. First, I have to mention that parenthesis can have the meaning of calling constructor in declarations in Cpp2: point: type = {
operator=: (out this, x: int, y: int) = {}
}
main: () = {
// These parenthesis call constructor.
x0: point = (0, 0);
} So with the help of this feature, we may create an array literal of constructors: // This is not a multidimensional array.
// Because parenthesis call constructor. They don't add new dimension.
// It's not ambiguous. It's expressed from the type within declaration.
x0: std::vector<point> = [(0, 0), (1, 1), (2, 2)];
// This is a two-dimensional array.
// Because the inner most parenthesis call constructor. They don't add new dimension.
// It's not ambiguous. It's expressed from the type within declaration.
x1: matrix<point, 2, 2> = [((0, 0), (1, 1)), ((2, 2), (3, 3))]; Because the type is specified within declaration, "Parenthesis for Constructors" won't conflict with "Parenthesis for Dimenstions". Finally a dictionary will look like this when its type is specified within declaration: c0: std::vector<std::pair<std::string, int>> = [("a", 1), ("b", 2), ("c", 3)];
// It's a dictionary.
c1: dictionary<int> = [("a", 1), ("b", 2), ("c", 3)]; It's context-free, and |
Beta Was this translation helpful? Give feedback.
-
Readability examplesC++ already has // `something` is defined somewhere...
sum: = 0;
(copy i: = 0) while i < 10 next i++ {
// Is there any array here?
sum += thing(something(i))(i);
} What does it? OK. // `something` is defined somewhere...
sum: = 0;
(copy i: = 0) while i < 10 next i++ {
// OK. This is an array of function objects.
sum += thing[something(i)](i);
}
In a similar manner, array literals with
They lead to more readable and easier to understand Cpp2 code, especially in a function call: If we use call((: t = (1, (f() + 2)), : t = (0, ()))); If you didn't lose the meaning of parenthesis, the compiler will face with ambiguous meanings. Now if we use call([: t = (1, [f() + 2]), : t = (0, [])]); Consider how you already understand what it does without my explanation (context-free). |
Beta Was this translation helpful? Give feedback.
-
I'm sympathetic to the suggestion, but this misunderstands the questions:
Bolding mine: The key word in those questions is "current". Current C++ code, current C++ guidance. |
Beta Was this translation helpful? Give feedback.
-
For a: = call(: () -> std::vector<int> = {1, 2, 3}); // {} is a list.
b: = call(: () -> std::vector<int> = { /*statements*/ }); // {} is a block statement.
c: = call(: () = {}); // {} is a block statement.
d: = call(: () -> _ = {}); // {} is a list. The only thing against Another Considered AlternativeAlso x: vector<int> = (1, 2); // This is an array of one `int` with value 2.
y: vector<int> = (1, 2):list; // This is an array of two `int`s with values 1 and 2.
But the problem of this approach is that we have to write // This is an array of two `int`s with values 1 and 2.
z: = (1, 2):list:vector<int>; On the other hand, if x: vector<int> = (1, 2); // This is an array of one `int` with value 2.
a: = (1, 2):vector; // The same
y: vector<int> = (1, 2):list; // This is an array of two `int`s with values 1 and 2.
b: = (1, 2):list:vector; // The same
z: vector<int> = (1, 2, 3); // This is an array of three `int`s with values 1, 2 and 3.
c: = (1, 2, 3):vector; // The same So the constructor with Abc: <T> type = {
// General constructor for sequence of `T`s
operator=: (out this, initializer_list<T>) = { /*statements*/ }
// This is a specialized constructor for one item of `T`
operator=: (out this, a: T) = { /*statements*/ }
// This is a specialized constructor for three items of `T`s
operator=: (out this, a: T, b: T, c: T) = { /*statements*/ }
}
main: () = {
// It calls the specialized constructor for one item of `int`.
a: Abc<int> = (1);
// It calls the general constructor for sequence of `int`s.
b: Abc<int> = (1, 2);
// It calls the specialized constructor for three items of `int`s.
c: Abc<int> = (1, 2, 3);
// It calls the general constructor for sequence of `int`s.
d: Abc<int> = (1, 2, 3, 4);
} By the way, it can be a bad API if those specialized constructors had completely different behaviours as |
Beta Was this translation helpful? Give feedback.
-
Sorry for misunderstanding, I thought the suggestion was about fixed length arrays. But I think cpp2 should support fixed length arrays as inbuilt as one can always resort to std::vector. |
Beta Was this translation helpful? Give feedback.
-
The idea of native arrays and tuples is something I really like for cpp2 as these contructs fit better at the language level rather than at a library level. Also, while this is more off-topic, does gsl's dynarray have a place in cpp2? |
Beta Was this translation helpful? Give feedback.
-
Makes it hard to know whether it is an array or tuple when named values are used to construct rather than literals, would need the context
On 7 September 2023 06:22:10 Sadeq ***@***.***> wrote:
That's a good idea. [...] can be used for both arrays and tuples if we could have P2163 in Cpp1:
a: = [10, 20, 30]; // an array of integers
t: = [10, "text"]; // a tuple of integer and string
—
Reply to this email directly, view it on GitHub<#637 (reply in thread)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQOLPIFC53JNVUUY6EDXZFKX7ANCNFSM6AAAAAA4OKIH6U>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I think
But there is a difference in Cpp1, So Cpp2 can just fix that by making them similar in regards to narrowing conversion. Also the following ambiguities about
If we write the above example with the new declaration syntax as discussed in #742 and #793: x0 : std::vector<int> = {}; // An empty vector
x1 : () -> void {} // An empty statement block
x2 : () -> std::vector<int> = {} // ERROR! It requires semicolon, and it's not visually ambiguous.
x3: = call(: () -> std::vector<int> = {}); // OK. {} is an empty vector. So in a nutshell Why Because making Optionally, the syntax of |
Beta Was this translation helpful? Give feedback.
-
So for example in Cpp1: // [2]
std::vector<int> var1(1, 2);
// [1, 2]
std::vector<int> var2{1, 2};
// ERROR!
std::vector<int> var3(1, 2, 3, 4, 5, 6); If it could create an object instead of For example in Cpp2: // [2]
var1: std::vector<int> = (1, 2);
// [1, 2]
var2: std::vector<int> = {1, 2};
// [1, 2, 3, 4, 5, 6]
var3: std::vector<int> = (1, 2, 3, 4, 5, 6); Instead of a compilation error in |
Beta Was this translation helpful? Give feedback.
-
Could cpp2 use the variadic "..." to denote initialiser lists, and then error when constructor signatures are ambiguous?
On 11 November 2023 10:24:36 Abhinav00 ***@***.***> wrote:
That's on the programmer, a language can only do so much. Cpp2 does great, the current problem is from a bad cpp1 choice that is std::initializer_list.
—
Reply to this email directly, view it on GitHub<#637 (reply in thread)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQLHTFRZPQHEDOBNMVLYD5G6DAVCNFSM6AAAAAA4OKIH6WVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM3TKMZZHAZDK>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
So this approach (making cls1: type = {
// : (out this, args: std::initializer_list<i32>)
operator=: (out this, more args: i32) = {}
// : (out this, next args: i32) = {}
// : (out this, ... -> args: i32) = {}
// : (out this, -> args: i32) = {}
// : (out this, args: i32 -> ...) = {}
}
cls2: type = {
// : (out this, args: std::initializer_list<_>)
operator=: (out this, more args) = {}
// : (out this, next args) = {}
// : (out this, ... -> args) = {}
// : (out this, -> args) = {}
// : (out this, args -> ...) = {}
} Also cls3: type = {
operator=: (out this, name: std::string, age: i32, more args: i32) = {}
// : (out this, name: std::string, age: i32, next args: i32) = {}
// : (out this, name: std::string, age: i32, ... -> args: i32) = {}
// : (out this, name: std::string, age: i32, -> args: i32) = {}
// : (out this, name: std::string, age: i32, args: i32 -> ...) = {}
}
var3: cls3 = ("Alex", 30, 1, 2, 3); For jagged arrays, multidimensional arrays and dictionaries, we will have: var1: std::vector<std::vector<i32>> = ((1, 2), (3, 4));
var2: matrix_2d<i32> = ((1, 2), (3, 4));
var3: std::vector<std::pair<std::string, i32>> = (("one", 1), ("two", 2)); And so on..., the type specifies the meaning of nested |
Beta Was this translation helpful? Give feedback.
-
Would this require the function to become templated? Or could it be lowered to cpp1 in such a way that it doesn't require a cpp1 feature, but maintains the benefits?
On 12 November 2023 04:45:46 Sadeq ***@***.***> wrote:
An alternative solution was that if a type has ambiguous constructor with initializer list, it should be a syntax error. By the way, this approach wouldn't fix the bad API design of std::vector.
std::initializer_list<T> is not the solution to have a safe variable argument list. They can be completely replaced with an integrated language feature. For example:
run: (args...: int) = {}
// : (args: int, ...) = {}
Now, syntactically Cpp2 can disallow a function (or constructor) to have the following overloads at the same time:
run: (arg1: int) = {}
run: (args...: int) = {} // ERROR!
// : (args: int, ...) = {} // ERROR!
The above example will be like the following example with std::initializer_list<int>:
run: (arg1: int) = {}
run: (args: std::initializer_list<int>) = {}
But because std::initiazlier_list<int> type is different from int, syntactically it has to be allowed to have all those overloads which leads to bad API design.
So this approach (making () to mean both construction and list initialization, and disallowing creating constructors with wrong signature) seems a good solution that also works in generic code, the syntax can be anything else such as using more/next contextual keyword or ... (in a way that it doesn't conflict with parameter packs of templates):
cls1: type = {
// : (out this, args: std::initializer_list<i32>)
operator=: (out this, more args: i32) = {}
// : (out this, next args: i32) = {}
// : (out this, ... -> args: i32) = {}
// : (out this, -> args: i32) = {}
}
cls2: type = {
// : (out this, args: std::initializer_list<_>)
operator=: (out this, more args) = {}
// : (out this, next args) = {}
// : (out this, ... -> args) = {}
// : (out this, -> args) = {}
}
Also args can be the last parameter, if Cpp2 will support combination of regular parameters with initializer_list:
cls3: type = {
operator=: (out this, name: std::string, age: i32, more args: i32) = {}
// : (out this, name: std::string, age: i32, next args: i32) = {}
// : (out this, name: std::string, age: i32, ... -> args: i32) = {}
// : (out this, name: std::string, age: i32, -> args: i32) = {}
}
var3: cls3 = ("Alex", 30, 1, 2, 3);
For jagged arrays, multidimensional arrays and dictionaries, we will have:
var1: std::vector<std::vector<i32>> = ((1, 2), (3, 4));
var2: matrix_2d<i32> = ((1, 2), (3, 4));
var3: std::vector<std::pair<std::string, i32>> = (("one", 1), ("two", 2));
And so on..., the type specifies the meaning of nested ()s.
—
Reply to this email directly, view it on GitHub<#637 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQKHRXJPPPUUCFM57EDYEBH7NAVCNFSM6AAAAAA4OKIH6WVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM3TKNBTGQ2DO>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
No, I mean, does the cpp1 have to be templated to achieve this safety? I believe that lowering to initialiser lists maintains the problem, and will allow the problematic constructors to compile regardless of the nice cpp2 syntax...?
On 12 November 2023 09:13:53 Sadeq ***@***.***> wrote:
No, it doesn't require the constructor to be templated. But yes, it can be lowered to Cpp1's initializer_list type.
For example, if the syntax of variable arguments is like this:
cls1: type = {
operator=: (out this, args: i32 -> ...) = {}
}
The type of args will be initializer_list<i32> in generated Cpp1 code.
The point is to have a special syntax to prevent bad API from happening because of initialized_list<TYPE> type in function overloading. But if it's reasonable to ban it without any special syntax:
cls2: type = {
operator=: (out this, x: i32) = {}
operator=: (out this, x: initializer_list<i32>) = {} // ERROR!
}
That's OK too (although syntactically it shouldn't be an error).
—
Reply to this email directly, view it on GitHub<#637 (reply in thread)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQIAJ7AZECTFSERX2QTYECHM3AVCNFSM6AAAAAA4OKIH6WVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM3TKNBUGI4DK>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Because the compiler will detect the collision, initialiser_list is a library feature, we are suggesting a language feature because then the compiler can actually detect the collisions
On 12 November 2023 09:24:52 Abhinav00 ***@***.***> wrote:
In that case, it would be just another way to spell the same feature. How does this prevent "bad" constructors?
—
Reply to this email directly, view it on GitHub<#637 (reply in thread)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQOWQECFYHUX6XAYSGLYECIWFAVCNFSM6AAAAAA4OKIH6WVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM3TKNBUGMZDO>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I don't see how the compiler can be expected to check that a library feature meets x, y, z requirements, I think it needs to be part of the language, whether that's a the cpp1 or 2 level
On 12 November 2023 11:52:50 Abhinav00 ***@***.***> wrote:
Cpp2 can have its own initializer_list which can be cast to std::initializer_list when required. OR what it can have is ARRAY LITERALS which can be cast to std:: initializer_list when required.
—
Reply to this email directly, view it on GitHub<#637 (reply in thread)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQJBD2HL7MPUMSYQGXDYEC2A7AVCNFSM6AAAAAA4OKIH6WVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM3TKNBVGM4DE>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
The syntaxI'm trying to find some good options for the list initialization syntax as a language feature. I think contextual keyword Using
|
Beta Was this translation helpful? Give feedback.
-
I've created issue #823 from our discussion. |
Beta Was this translation helpful? Give feedback.
-
Is there any update regarding initialization with array literals? Could we use them for array/vector etc literals? For example:
Pros:
Cons: Double the brackets required for the 1-D arrays (4 instead of 2). |
Beta Was this translation helpful? Give feedback.
-
Preface
The idea of this suggestion is gathered from discussion in this issue.
I have to mention that
(...)
is already for calling constructores, grouping expressions and initializing lists.Now, consider the following ambiguities:
(1)
as an expression, are they parenthesis around a value? Or is it an array of one item?()
is not an empty array, but it calls default constructor in variable declarations.(1, 2)
in declarations, is it the arguments of a constructor? Or is it an array of two items?Yes I know that
std::vector
has a bad API design, but I ask myself why would Cpp2 (like Cpp1) allow libraries to have this ambiguity in the first place?Having array literals with a different syntax, will solve those three ambiguities. I suggest to use
[...]
for array literals:Also nested
(...)
s or;
s or etc, will create multidimensional arrays, because they don't create a new array, and they are for mathematical grouping (as they are used to group expressions and to change the precedence of operators). On the other hand, nested[...]
s will create jagged arrays, because they create a new array:I currently do not suggest to support multidimensional arrays, but it's a possibility to consider in the future.
Suggestion Detail
Three options are available instead of
()
for array literals:<...>
is already for template parameters/arguments. It's not a good choice, because:<a < b, c > 2>
.[...]
is already for accessing items of an array. It seems to be a good choice.{...}
is already for function/statement blocks and type definitions. It can be considered as a good choice.Now, it's the time to compare both
[...]
and{...}
for array literals:OK. Both of them look good. So what if we want to write an empty array?
[]
is clearly an empty array, but{}
can be either an empty function/statement block or an empty array in which it depends on the declaration. For example:{}
is visually surprising and inconsistent forx1
,x2
andx3
, although they look the same:x0
declaration,{}
is an empty array.x1
declaration,{}
is an empty statement block.x2
declaration,{}
is an error, because it must end with;
.x3
declaration,{}
is an empty array!So
[...]
is more expressive than{...}
for array literals.Now let's consider this situation in the following example:
The first
[...]
creates an array, and the second[...]
accesses an item from it. A sequence of[...]
s is not ambiguous, because its behaviour is similar to parenthesis:x0: = (call() + something)(1);
The first
(...)
groups the operands ofoperator+
, and the second(...)
callsoperator()
on the result.Your Questions
Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?
Yes. If a bad API design can suddenly change the meaning of code, it's going to be a security vulnerability. This suggestion is a way to prevent it by separating arrays from constructors and expressions.
Will your feature suggestion automate or eliminate X% of current C++ guidance literature?
Yes. It's not needed to learn if user-defined constructors are ambiguous with initializer lists, because it prevents ambiguous situation completely. It allows more API choices.
Considered Alternatives
An alternative solution was that if a type has ambiguous constructor with initializer list, it should be a syntax error. By the way, this approach wouldn't fix the bad API design of
std::vector
.Another alternative solution was a little complicated. The idea was to favor constructors over initializer list, and to consider a comma-separated list with parenthesis to be an initializer list:
With the help of unnamed variable declaration and indirect initialization, it could be used like this:
But I gave up on this idea, becuase it would encourage unnamed variable declaration more than necessary.
Finally I considered to use literal templates syntax:
But I gave up on this idea too, because
(1, 2)<int>
would require to always specify the type, and(1, 2)list
would make user-defined literal suffixes to be look like constructors.Edits
Beta Was this translation helpful? Give feedback.
All reactions