Why are many curly brackets treated differently by C++ compilers? - c++

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.

Related

Constructor taking std::initializer_list is preferred over other constructors

I compile this code below with GCC 11.1.0 with a flag -std=c++17. It occurs that on the stdout is printed initializer_list.
I compiled the same code with MSVC with the flag -std=c++17 but it printed "copy constructor". Which compiler is more compliant with the cpp standard? Is compiler free to choose one of the constructors?
#include <iostream>
using namespace std;
struct S
{
S(int) { }
S(initializer_list<int>) { cout << "initializer_list"; }
S(const S&) { cout << "copy constructor"; }
operator int() const { return 1; };
};
int main()
{
S s1(20);
S s2{ s1 };
}
The compiler is pretty much never "free to choose" for stuff like this. If it were, we wouldn't be able to write pretty much any portable C++ code.
[over.match.list] does give priority to initializer_list constructors. Constructor function overloading under the rules of list initialization gets invoked at step 3.6. Steps 3.1-3.5 do not apply, as your type doesn't qualify for any of those cases. Step 3.1 is particularly interesting, as it is specifically meant to invoke copy constructors instead of doing other things, but it also only applies to aggregates. Which your type is not.
Since your type is implicitly convertible to int, and your type takes an initializer_list<int>, there is a valid way to build an initializer_list that matches a constructor for the type in question. Therefore, this is the constructor [over.match.list] will select.
So in this case, VC++ is wrong. As is Clang, apparently.

Empty braces magic in initializer lists

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.

Default constructor of T is bypassed when {} are used with new to create std::array<T, n>

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.

direct-initialization vs direct-list-initialization (C++)

DIRECT- VS COPY-INITIALIZATION
Through this question (Is it direct-initialization or copy-initialization?) I learned the differences between direct-initialization and copy-initialization:
direct-initialization copy-initialization
----------------------- ---------------------
obj s("value"); obj s = obj("value");
obj s = "value";
obj s{"value"}; obj s = {"value"};
obj s = obj{"value"};
I mention it here for the sake of completeness. My actual questions for this page are listed in the next paragraph >>
DIRECT-INITIALIZATION VS DIRECT-LIST-INITIALIZATION
The answers revealed that within the category of direct-initialization, one can make a difference between direct-initialization and direct-list-initialization.:
obj s("value"); // direct-initialization
obj s{"value"}; // direct-list-initialization
I know that list-initialization doesn't allow narrowing, such that an initialization like int x{3.5}; won't compile. But besides this, I got a couple of questions:
(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};?
Let's consider a compiler without any optimizations. I would like to know any possible technical difference :-)
(2) Perhaps I should ask the exact same question for a multi-variable initialization, like:
obj s("val1", "val2"); and obj s{"val1", "val2"};
(3) I have noticed that the list-initialization can sometimes call a different constructor, like in:
vector<int> a{10,20}; //Curly braces -> fills the vector with the arguments
vector<int> b(10,20); //Parentesis -> uses arguments to parameterize some functionality
How is that possible?
DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE?
From my limited knowledge on C++, I believe that all possible initializations of objects (either native-typed or user-defined-typed objects) have been covered in the examples above. Is that correct? Did I overlook something?
PS: I am learning C++ (I do know C, but not yet C++), so please don't be too hard on me ;-)
(1) Is there any difference in compiler output between
obj s("value"); and obj s{"value"};? Let's consider a compiler
without any optimizations. I would like to know any possible technical
difference :-)
(2) Perhaps I should ask the exact same question for a
multi-variable initialization, like: obj s("val1", "val2"); and
obj s{"val1", "val2"};
(3) I have noticed that the list-initialization can
sometimes call a different constructor, like in:
vector<int> a{10,20}; //Curly braces -> fills the vector with the arguments
vector<int> b(10,20); //Parentesis -> uses arguments to parameterize some functionality
How is that possible?
If there is an initializer-list constructor for the class type obj, it will always be preferred over other other constructors for brace-init-initializers (list initialization), in your case obj s{"value"};;
What this means is that if you have a constructor that takes std::initializer_list<T> as its first parameter and other parameters are defaulted, then it is preferred. Example
struct A{
A(std::initializer_list<std::string>); //Always be preferred for A a{"value"}
A(std::string);
};
std::vector<T> and other STL containers have such initializer-list constructors.
Otherwise, Overload resolution kicks in and it falls back to any available constructor as selected by the overload resolution process;
Otherwise, if the class has no user defined constructors and it's an aggregate type, it initializes the class members directly.
DID WE COVER ALL POSSIBLE INITIALIZATIONS HERE? From my
limited knowledge on C++, I believe that all possible initializations
of objects (either native-typed or user-defined-typed objects) have
been covered in the examples above. Is that correct? Did I overlook
something?
Nope. you didn't. Excluding Reference initialization, there are five ways objects may be initialized in C++.
Direct Initialization
List Initialization
Copy Initialization
Value Initialization
Aggregate Initialization (only for aggregate types)
You can find more information here
List-initialization guarantee left-to-right order of arguments evaluation. In this example we will create std::tuple from istream data and then output tuple example can be found here:
#include <iostream>
#include <sstream>
#include <tuple>
template<typename T, typename CharT>
T extract(std::basic_istream<CharT>& is) {
T val;
is >> val;
return val;
}
void print(const std::tuple<int, long, double>& t) {
using std::cout;
cout << std::get<0>(t) << " " << std::get<1>(t) << " " << std::get<2>(t) << std::endl;
}
int main()
{
std::stringstream ss1;
std::stringstream ss2;
ss1 << 1 << " " << 2 << " " << 3;
ss2 << 1 << " " << 2 << " " << 3;
auto compilerOrder = std::tuple<int, long, double>( extract<int>(ss1), extract<long>(ss1), extract<double>(ss1) );
auto leftToRightOrder = std::tuple<int, long, double>{ extract<int>(ss2), extract<long>(ss2), extract<double>(ss2) };
print(compilerOrder);
print(leftToRightOrder);
}
Output:
3 2 1
1 2 3
As you can see, the difference will be seen then we use multiple times same stream-like resource inside function brackets.
Also my question about that

Calling an explicit constructor with a braced-init list: ambiguous or not?

Consider the following:
struct A {
A(int, int) { }
};
struct B {
B(A ) { } // (1)
explicit B(int, int ) { } // (2)
};
int main() {
B paren({1, 2}); // (3)
B brace{1, 2}; // (4)
}
The construction of brace in (4) clearly and unambiguously calls (2). On clang, the construction of paren in (3) unambiguously calls (1) where as on gcc 5.2, it fails to compile with:
main.cpp: In function 'int main()':
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
B paren({1, 2});
^
main.cpp:6:5: note: candidate: B::B(A)
B(A ) { }
^
main.cpp:5:8: note: candidate: constexpr B::B(const B&)
struct B {
^
main.cpp:5:8: note: candidate: constexpr B::B(B&&)
Which compiler is right? I suspect clang is correct here, as the ambiguity in gcc can only arise through a path that involves implicitly constructing B{1,2} and passing that to the copy/move constructor - yet that constructor is marked explicit, so such implicit construction should not be allowed.
As far as I can tell, this is a clang bug.
Copy-list-initialization has a rather unintuitive behaviour: It considers explicit constructors as viable until overload resolution is completely finished, but can then reject the overload result if an explicit constructor is chosen. The wording in a post-N4567 draft, [over.match.list]p1
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy-initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]
clang HEAD accepts the following program:
#include <iostream>
using namespace std;
struct String1 {
explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
String2(const char*) { cout << "String2\n"; }
};
void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }
int main()
{
//f1( {"asdf"} );
f2( {"asdf"} );
f( {"asdf"} );
}
Which is, except for commenting out the call to f1, straight from Bjarne Stroustrup's N2532 - Uniform initialization, Chapter 4. Thanks to Johannes Schaub for showing me this paper on std-discussion.
The same chapter contains the following explanation:
The real advantage of explicit is that it renders f1("asdf") an
error. A problem is that overload resolution “prefers” non-explicit
constructors, so that f("asdf") calls f(String2). I consider the
resolution of f("asdf") less than ideal because the writer of
String2 probably didn’t mean to resolve ambiguities in favor of
String2 (at least not in every case where explicit and non-explicit
constructors occur like this) and the writer of String1 certainly
didn’t. The rule favors “sloppy programmers” who don’t use explicit.
For all I know, N2640 - Initializer Lists — Alternative Mechanism and Rationale is the last paper that includes rationale for this kind of overload resolution; it successor N2672 was voted into the C++11 draft.
From its chapter "The Meaning Of Explicit":
A first approach to make the example ill-formed is to require that all
constructors (explicit and non-explicit) are considered for implicit
conversions, but if an explicit constructor ends up being selected,
that program is ill-formed. This rule may introduce its own surprises;
for example:
struct Matrix {
explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);
struct Pixel {
Pixel(int row, int col);
};
Pixel transpose(Pixel);
Pixel p = transpose({x, y}); // Error.
A second approach is to ignore the explicit constructors when looking
for the viability of an implicit conversion, but to include them when
actually selecting the converting constructor: If an explicit
constructor ends up being selected, the program is ill-formed. This
alternative approach allows the last (Pixel-vs-Matrix) example to work
as expected (transpose(Pixel) is selected), while making the
original example ("X x4 = { 10 };") ill-formed.
While this paper proposes to use the second approach, its wording seems to be flawed - in my interpretation of the wording, it doesn't produce the behaviour outlined in the rationale part of the paper. The wording is revised in N2672 to use the first approach, but I couldn't find any discussion about why this was changed.
There is of course slightly more wording involved in initializing a variable as in the OP, but considering the difference in behaviour between clang and gcc is the same for the first sample program in my answer, I think this covers the main points.
This is not a complete answer, even though it is too long as a comment.
I'll try to propose a counterexample to your reasoning and I'm ready to see downvote for I'm far from being sure.
Anyway, let's try!! :-)
It follows the reduced example:
struct A {
A(int, int) { }
};
struct B {
B(A) { }
explicit B(int, int ) { }
};
int main() {
B paren({1, 2});
}
In this case, the statement {1, 2} gives place apparently to two solutions:
direct initialization by means of B(A), because A(int, int) is not explicit and thus it is allowed and that's actually the first candidate
for the same reason above, it can be interpreted as B{B(A{1,2})} (well, let me abuse the notation to give you an idea and what I mean), that is {1,2} allows the construction of a B temporary object that is used immediately after as an argument for the copy/move constructor, and it's allowed again because the involved constructors are not explicit
The latter would explain the second and the third candidates.
Does it make sense?
I'm ready to delete the answers as long as you explain me what's wrong in my reasoning. :-)