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.