Consider the following minimal example:
#include <iostream>
struct X {
X() { std::cout << "Default-ctor" << std::endl; }
X(std::initializer_list<int> l) {
std::cout << "Ilist-ctor: " << l.size() << std::endl;
}
};
int main() {
X a{};
X b({}); // reads as construct from {}
X c{{}}; // reads as construct from {0}
X d{{{}}}; // reads as construct from what?
// X e{{{{}}}}; // fails as expected
}
Godbolt example
I have no questions about a, b and c, everything is rather clear
But I can not understand why d works
What this additional pair of braces in d stands for? I looked up C++20 standard, but I can not find answer easily. Both clang and gcc agree on this code, so it is me who misses something
A nice trick to do to get information about what the compiler does, is to compile using all errors:
-Weverything. Let's see the output here (for d only):
9.cpp:16:6: warning: constructor call from initializer list is incompatible with C++98
[-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~~~~~
X::X(std::initializer_list) is called.
9.cpp:16:8: warning: scalar initialized from empty initializer list is incompatible with
C++98 [-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~
Scalar (int) initialized in inner {}. So we have X d{{0}}.
9.cpp:16:7: warning: initialization of initializer_list object is incompatible with
C++98 [-Wc++98-compat]
X d{{{}}}; // reads as construct from what?
^~~~
5 warnings generated.
std::initializer_list is initialized from {0}. So we have X d{std::initializer_list<int>{0}};!
This shows us everything we need. The extra bracket is for constructing the initializer list.
Note: If you want to add extra brackets you can by invoking the copy/move constructor (or eliding it), but C++ compilers won't do it implicitly for you to prevent errors:
X d{X{{{}}}}; // OK
X e{{{{}}}}; // ERROR
Thought I'd just illustrate:
X d{ { {} }};
| | |
construct an | |
`X` from ... an initializer_list |
containing... int{}
The rules for list-initialization are to find an initializer_list<T> constructor and use it if at all possible, otherwise... enumerate the constructors and do the normal thing.
With X{{}}, that is list-initialization: the outermost {}s are the initializer_list and this contains one element: the {}, which is 0. Straightforward enough (though cryptic).
But with X{{{}}}, this doesn't work anymore using the outermost {} as the initializer_list because you can't initialize an int from {{}}. So we fallback to using constructors. Now, one of the constructors takes an initializer_list, so it's kind of like starting over, except that we'd already peeled off one layer of braces.
This is why for instance vector<int>{{1, 2, 3}} works too, not just vector<int>{1, 2, 3}. But like... don't.
Related
In the following C++20 program I put by mistake one extra pair of curved braces {} in B{{{{A{}}}}}:
#include <iostream>
struct A
{
A() { std::cout << "A() "; }
A( A&& ) = delete;
~A() { std::cout << "~A() "; }
};
struct B { std::initializer_list<A> l; };
int main()
{
[[maybe_unused]] auto x = B{{{{A{}}}}};
std::cout << ". ";
}
Clang rejected it, however with a strange error:
error: call to deleted constructor of 'const A'
But to my surprise GCC accepted it( https://gcc.godbolt.org/z/aPWe13xfc ).
Could you please explain why GCC accepts it (how does it treat extra curved braces)?
B{…}, since the single element of the initializer list is not designated and is not of type B (as it has no type at all), is aggregate initialization ([dcl.init.list]/3.4). B::l is thus copy-initialized from {{{A{}}}}; it's a specialization of std::initializer_list, so /3.6 and /5 apply. An "array of 1 const A" is created, and {{A{}}} is the initializer for its single element.
We can thus reduce the code to
const A a = {{A{}}};
with no mention of B at all, and indeed Clang and GCC produce the same disagreement for this line. Clang appears to be correct to reject it: that initialization is sent to constructors by /3.7, and obviously there is no constructor that could be viable (thus the error about the deleted move constructor).
Oddly, removing the extra pair of braces here (or in the original) causes both compilers to accept:
const A a = {A{}};
despite the fact that A is not an aggregate and so /3.7 still applies. Presumably both compilers are overenthusastically performing "guaranteed copy elision" (albeit to different degrees), identifying the prvalue A{} with an object eventually to be initialized by it; that, however, takes place only per [dcl.init.general]/16.6.1, which never comes into play in this analysis.
Let's consider this class:
class A {
public:
A() = delete;
A(int i) :
i_m(i) {
std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
}
~A() {
std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
}
private:
int i_m{1234567890};
};
The default constructor is explicitly deleted so AFAIK A can only be constructed from an integer. The default initialization of data member i_m will never be used.
Let'ts consider this program:
int main() {
using T = std::array<A, 2>;
//T a;
// error: use of deleted function 'std::array<A, 2>::array()'
// note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed
// error: use of deleted function 'A::A()'
//T b{};
// error: use of deleted function 'A::A()'
}
Again, this seems completely fine to me.
Let's now consider this other program:
int main() {
using T = std::array<A, 2>;
auto foo = new T{};
delete foo;
auto foo_init = new T{1, 2};
delete foo_init;
// auto zorg = new T();
// delete zorg;
// error: use of deleted function 'std::array<A, 2>::array()'
// note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed:
// error: use of deleted function 'A::A()'
auto zorg_init = new T({3, 4});
delete zorg_init;
}
This code does compile (with no warning) and generates the following output:
A::~A() 0
A::~A() 38870160
A::A(int) 1
A::A(int) 2
A::~A() 2
A::~A() 1
A::A(int) 3
A::A(int) 4
A::~A() 4
A::~A() 3
And now something seems not fine to me. How it that possible that the line auto foo = new T{}; is not considered ill-formed? The initialization of class A is completely bypassed here.
It is worth nothing that this code doesn't compile:
int main() {
auto p = new A{};
delete p;
}
The error being the expected:
error: use of deleted function 'A::A()'
I tested those codes with the following options: -Wall -Wextra -pedantic -std=c++17. gcc -v gives: gcc version 7.2.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) on my computer.
Any explanation will be greatly appreciated :)
PS : after exhaustive tests in Compiler Explorer (using with sample https://godbolt.org/z/5VZLU_), it seems that this is an issue in gcc 7.x. Indeed, only 7.x versions of gcc compile this sample. gcc 6.4 doesn't not. gcc 8.1 neither. clang neither. msvc neither.
Can you confirm that there is no UB here and this is really a compiler bug?
The fact that T b{}; doesn't compile while new T{} makes it evident that this is a compiler bug anyway. However, the standard itself is probably defective. Here's why.
The technical answer to this question is: the code is well-formed, because the standard specifically says so. [array.overview]/2
An array is an aggregate that can be list-initialized with up to N
elements whose types are convertible to T.
The initializer {} indeed contains "up to N elements whose types are convertible to T" — it contains 0 elements, all of which are convertible to A.
How are the elements initialized then? The standard isn't really clear on this. Here's what [array.overview]/3 says:
An array satisfies all of the requirements of a container and of a
reversible container ([container.requirements]), except that a
default constructed array object is not empty and that swap does
not have constant complexity. [...]
(emphasis mine)
The standard does not mention how the elements of this non-empty array are initialized.
Suppose that std::array is implemented like this (for N == 2):
template <class T, std::size_t N>
struct array {
T __elem[N];
};
Then the elements are copy-initialized from {} according to [dcl.init.aggr]/8:
If there are fewer initializer-clauses in the list than there are
elements in a non-union aggregate, then each element not explicitly
initialized is initialized as follows:
(8.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
(8.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
(8.3) Otherwise, the program is ill-formed.
[...]
which is ill-formed according to [dcl.init.list]/3 (the deleted default constructor is selected).
This means that the standard considers the common implementation to be wrong, which is probably not intended.
Consider the code
#include <iostream>
class Foo
{
int val_;
public:
Foo(std::initializer_list<Foo> il)
{
std::cout << "initializer_list ctor" << std::endl;
}
/* explicit */ Foo(int val): val_(val)
{
std::cout << "ctor" << std::endl;
};
};
int main(int argc, char const *argv[])
{
// why is the initializer_list ctor invoked?
Foo foo {10};
}
The output is
ctor
initializer_list ctor
As far as I understand, the value 10 is implicitly converted to a Foo (first ctor output), then the initializer constructor kicks in (second initializer_list ctor output). My question is why is this happening? Isn't the standard constructor Foo(int) a better match? I.e., I would have expected the output of this snippet to be just ctor.
PS: If I mark the constructor Foo(int) as explicit, then Foo(int) is the only constructor invoked, as the integer 10 cannot now be implicitly converted to a Foo.
§13.3.1.7 [over.match.list]/p1:
When objects of non-aggregate class type T are list-initialized
(8.5.4), overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of
the initializer list as a single argument.
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all
the constructors of the class T and the argument list consists of
the elements of the initializer list.
If the initializer list has no elements and T has a default
constructor, the first phase is omitted. In copy-list-initialization,
if an explicit constructor is chosen, the initialization is
ill-formed.
As long as there is a viable initializer-list constructor, it will trump all non-initializer-list constructors when list-initialization is used and the initializer list has at least one element.
The n2100 proposal for initializer lists goes into great detail about the decision to make sequence constructors (what they call constructors that take std::initializer_lists) to have priority over regular constructors. See Appendix B for a detailed discussion. It's succinctly summarized in the conclusion:
11.4 Conclusion
So, how do we decide between the remaining two alternatives (“ambiguity” and “sequence constructors take priority
over ordinary constructors)? Our proposal gives sequence constructors
priority because
Looking for ambiguities among all the constructors leads to too many “false positives”; that is, clashes between apparently unrelated
constructors. See examples below.
Disambiguation is itself error-prone (as well as verbose). See examples in §11.3.
Using exactly the same syntax for every number of elements of a homogeneous list is important – disambiguation should be done for
ordinary constructors (that do not have a regular pattern of
arguments). See examples in §11.3. The simplest example of a false
positive is the default constructor:
The simplest example of a false positive is the default constructor:
vector<int> v;
vector<int> v { }; // potentially ambiguous
void f(vector<int>&);
// ...
f({ }); // potentially ambiguous
It is possible to think of classes where initialization with no
members is semantically distinct from default initialization, but we
wouldn’t complicate the language to provide better support for those
cases than for the more common case where they are semantically the
same.
Giving priority to sequence constructors breaks argument checking into
more comprehensible chunks and gives better locality.
void f(const vector<double>&);
// ...
struct X { X(int); /* ... */ };
void f(X);
// ...
f(1); // call f(X); vector’s constructor is explicit
f({1}); // potentially ambiguous: X or vector?
f({1,2}); // potentially ambiguous: 1 or 2 elements of vector
Here, giving priority to sequence constructors eliminates the
interference from X. Picking X for f(1) is a variant of the problem
with explicit shown in §3.3.
The whole initializer list thing was meant to enable list initialisation like so:
std::vector<int> v { 0, 1, 2 };
Consider the case
std::vector<int> v { 123 };
That this initializes the vector with one element of value 123 rather than 123 elements of value zero is intended.
To access the other constructor, use the old syntax
Foo foo(10);
Initializing a struct with default values is trivial:
struct X { int a; int b = 2; };
and initializing a struct with a brace initializer is trivial too:
X x = {1, 3};
Suprisingly the init code won't compile, until I remove the default value. So, how would I do the init in such a case? I'd like to keep X a POD without c-tor.
Here is some documentation relevant to the problem:
http://en.cppreference.com/w/cpp/language/aggregate_initialization
In c++11 your code is invalid. In c++14 it is valid again.
In C++11 adding a default initialization prevents braced init from being valid. In C++14, it does not.
A way to solve your problem in C++11 would be to write a constructor with the value for a and the b value with a default.
Simple question about C++11 syntaxis. There is a sample code (reduced one from source)
struct Wanderer
{
explicit Wanderer(std::vector<std::function<void (float)>> & update_loop)
{
update_loop.emplace_back([this](float dt) { update(dt); });
}
void update(float dt);
};
int main()
{
std::vector<std::function<void (float)>> update_loop;
Wanderer wanderer{update_loop}; // why {} ???
}
I'd like to know, how it can be possible call constructor with curly brackets like Wanderer wanderer{update_loop}; It is neither initializer list, nor uniform initialization. What's the thing is this?
It is neither initializer list, nor uniform initialization. What's the thing is this?
Your premise is wrong. It is uniform initialization and, in Standardese terms, direct-brace-initialization.
Unless a constructor accepting an std::initializer_list is present, using braces for constructing objects is equivalent to using parentheses.
The advantage of using braces is that the syntax is immune to the Most Vexing Parse problem:
struct Y { };
struct X
{
X(Y) { }
};
// ...
X x1(Y()); // MVP: Declares a function called x1 which returns
// a value of type X and accepts a function that
// takes no argument and returns a value of type Y.
X x2{Y()}; // OK, constructs an object of type X called x2 and
// provides a default-constructed temporary object
// of type Y in input to X's constructor.
It is just C++11 syntax. You can initialize objects calling their constructor with curly braces. You just have to bear in mind that if the type has an initializer_list constructor, that one takes precedence.
In addition, braces-constructors do not allow narrowing, similarly to braces-initialization.
Let's take a look at the simple constructor taking and printing an integer value:
class Test {
public:
Test(int i) {
std::cout << i << std::endl;
}
};
While Test test(3.14); compiles and outputting narrowed 3,
Test test{3.14}; would not even compile:
error: narrowing conversion of ‘3.1400000000000001e+0’ from ‘double’ to ‘int’ [-Wnarrowing]
11 | Test test{3.14};
| ^