Does the standard define list initialization with superfluous braces (e.g. T{{{10}}})?

  • A+
Category:Languages

When using a braced-init-list containing multiple braced-init-list, what are the rules defined by the standard for B, C, and D?

For B, I believe this scenario is defined within the standard as a braced-init-list with a single element and therefore it calls Test(int) directly with no temporary - but I am unable to find where.

For C and D, I am unsure of whether this is undefined behavior or not.

I am also interested in what occurs when using more than a single element i.e. {{{1, 2}}} and if this changes the behavior for B, C, or D?

#include <iostream>  struct Test {     Test(const int a) {         // A and B call this     }      Test(Test&& test) = delete;     Test(const Test& test) = delete; };  int main() {     Test a{1}; // calls Test(int)     Test b{{2}}; // B     Test c{{{3}}}; // C     Test d{{{{4}}}}; // D     // Test e{a}; error, deleted copy constructor     // Test f{Test{0}}; error, deleted move constructor     return 0; } 

GCC g++ my_prog.cpp gives me an error for C and D only:

my_prog.cpp: In function 'int main()': my_prog.cpp:16:17: error: too many braces around initializer for 'int' [-fpermissive]      Test c{{{3}}};                  ^ my_prog.cpp:4:14: note:   initializing argument 1 of 'Test::Test(int)'      Test(int a) {           ~~~~^ 

 


When you have

Test b{{2}};  

[dcl.init.list]/3.7 states.

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]). [...]

and looking in [over.match] we have [over.match.ctor]/1

When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer.

So we consider the constructors, find

Test(const int a) 

and then we use the element {2} as initializer for a which uses [dcl.init.list]/3.9

Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

With

Test c{{{3}}}; // and Test d{{{{4}}}}; 

we do the same thing. We look at the constructors and find

Test(const int a) 

as the only viable one. When we do and try to initialize a, we look to [dcl.init.list]/3.9 again but it doesn't apply here. {{3}} and {{{4}}} aren't initializer lists with a single type E. A braced-init-list doesn't have a type so we have to keep going list in [dcl.init.list]/3. When we do we don't meet anything else that matches until [dcl.init.list]/3.12

Otherwise, the program is ill-formed.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: