Consider the following toy code:
class Y
{
public:
Y(int, int) { cout << "Y ctor\n"; }
};
class X
{
public:
//X(initializer_list<int>) { cout << "init\n"; } // 1
X(Y&&) { cout << "Y&&\n"; } // 2
X(int, int) { cout << "param\n"; } // 3
X(const X&) { cout << "X&\n"; } // 4
X(const Y&) { cout << "Y&\n"; } // 5
X(Y) { cout << "Y\n"; } // 6
X(X&&) { cout << "X&&\n"; } // 7
};
void f(const X&) { }
void f(const Y&) { }
void f(Y) { }
int main()
{
X x({1,2});
//f({ 1,2 }); // 8
}
I'm trying to understand how the compiler uses {1,2} in X x({1,2}). Followings are my understandings:
Uncomment line 1
In such case, I think X x({1,2}) is an explicit call for X(initializer_list<int>). That is, when compiler sees {}-list, I think it will first see it as std::initializer_list and a constructor that takes an argument of initializer_list<T> will be the better match than all the others.
Comment out line 6, 7
In such case, I think {1,2} is used to construct an object of Y which is bound with an rvalue reference (i.e. compiler chooses to call X(Y&&)). My guess here is that the compiler treats {1,2} as some kind of rvalue when there is no constructor that takes an argument of initializer_list<T>, and thus a constructor that takes an rvalue reference is preferred (in this case, X(Y&&) is the only constructor of this kind).
Update: I found that the behavior I described in this bullet point is actually a bug of MSVC. gcc and clang will report ambiguity, not calling X(Y&&)
Comment out line 7
Ambiguity arises. Compiler reports that both X(Y&&) and X(Y) match. I think this is because although X(Y&&) is a better match than X(const Y&) and X(const X&), it is indistinguishable to X(Y). (I think the logic here is quite twisted)
Possible reference.
Comment out line 2,7
The code compiles and prints param. I think this time X(const X&) is chosen but I'm not sure why. My guess for this is that, when all the viable matches (X(const X&), X(const Y&) and X(Y)) are indistinguishable, the compiler chooses X(const X&) because X x({1,2}); is constructing an X.
Update: this can also be a MSVC bug. gcc and clang will report ambiguity, not calling X(const X&)
Comment out line 2,7 and uncomment line 8
I think this is the case where all the viable matches are indistinguishable and f is not a constructor of X, hence f(const X&) is not treated specially, and ambiguity arises.
May I ask if my understandings are correct? (I highly doubt they are accurate)
I find that it is quite tedious to read out what constructor is invoked by things like X x({1,2});, so I guess in real code we should try to avoid such shorthand and write code in a more explicit way...
On the other hand, if we use the same settings for class X and Y as above, define and call a function g as follows:
X g()
{
return { 1,2 };
}
int main()
{
g();
}
The results always seem to be equivalent as initializing the returned temporary by X{1,2} (i.e. call X(initializer_list<int>) when it exists, and in all the other cases call X(int, int)). May I ask if this is true?
X x({1,2}) can’t be an “explicit call for” a std::initializer_list: a braced-init-list has no type. However, there is a rule saying that one is a better match for a specialization of that template than for any other class type.
X(Y&&) is used because X(const X&) isn’t viable. It would be considered a second user-defined conversion even though {…}→Y→X isn’t (because of the two layers of initialization).
Yes, Q and Q&& are often ambiguous like that. Consider that C++17 prvalues don’t even move to initialize either.
Presumably you meant you commented out lines 2 and 6. In that case, the constructors from (references to) X are disqualified as above, and X(const Y&) is a worse match because the const must be added to the non-const list-initialized prvalue. (All user-defined conversions are otherwise indistinguishable.)
f(Y) is better than the other two regardless of X’s constructors for the same const reason.
As for g, yes copy-list-initialization is much the same as direct-list-initialization in the absence of explicit constructors. Since it is list-initialization, rather than direct initialization from an “argument” that is a braced-init-list, it does prefer an initializer-list constructor over all others.
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.
I have the following code
struct R {
int i_ = 8;
R() = default;
R(R const& o) = default;
R(R&& o) = default;
R(int i) : i_(i) {}
operator int() const { return i_; }
};
struct S {
R i_;
operator R() const { return i_; }
operator int() const { return static_cast<int>(i_); }
};
int main() {
S s;
R r0(s);
R r = static_cast<R>(s);
float af[] = {1,2,3};
float f1 = af[s];
float f2 = af[r];
}
which compiles fine on Clang and GCC for C++17 and C++20 (not for C++ < 17) but complains about
'R::R': ambiguous call to overloaded function
note: could be 'R::R(R &&)'
note: or 'R::R(int)'
in MSVC for all available standards.
I tried to find a difference in the language conformances by comparing MSVC, Clang and GCC and also tried /permissive- for MSVC but couldn't find any explanation yet.
Who is right (any why) and
how can I make it compile for MSVC without giving up on the R&& and int i ctors in R or marking the cast operator int()s explicit?
Here's a rather crowded godbolt including a possible workaround.
This is CWG 2327, which doesn't currently have a resolution but gcc and clang seem to do "the right thing." The issue reads:
Consider an example like:
struct Cat {};
struct Dog { operator Cat(); };
Dog d;
Cat c(d);
This goes to 9.4 [dcl.init] bullet 17.6.2:
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one is chosen through overload resolution (12.2 [over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 9.4.4 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.
This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.
The case here is similar, just more complicated. With either:
R r0(s);
R r = static_cast<R>(s);
We have a conversion function from s that gives a prvalue R, which is a better path to take than any of the other paths at our disposal -- whether doing through R's move constructor or R(int). But we just don't have a rule that says that that's what we're supposed to do.
gcc and clang seem to implement the desired behavior on C++17 or later (where we have guaranteed copy elision) despite not having any wording for what the desired behavior is. msvc on the other hand seems to follow the rules as specified (which I think do specify this case as being ambiguous).
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);
#include <vector>
using namespace std;
class A
{
public:
explicit A(const initializer_list<int> & a) {}
};
void func(const vector<int>& a)
{
}
void func(A a)
{
}
int main(void)
{
func({ 1,2,3 });
}
This code fails to compile:
(19): error C2668: 'func': ambiguous call to overloaded function
(13): note: could be 'void func(A)'
(9): note: or 'void func(const std::vector> &)'
with[_Ty=int]
(19): note: while trying to match the argument list '(initializer list)'
Note that I specified 'explicit' on A's constructor.
In my view, func(A a) should not be considered as a candidate of {1,2,3}. And actually, it is not. If I remove func(const vector<int>& a), then the code still fails, instead of succeeding by removing ambiguity.
In summary, in this code, the func(const vector<int>& a) is the only callable function for {1,2,3}, so there is no ambiguity.
My question is..
How does C++ overloading resolution procedures come to conclusion of 'ambiguous'?
Why doesn't C++ just simply choose callable one?
explicit constructors are not ignored when you perform list initialization. Such constructors are always considered as viable overload candidates. What happens is, if the system attempts to call an explicit constructor under copy-list-initialization (ie: after overload resolution), then you get a hard compile error.
In your case, it never gets that far because the overload set is ambiguous.
explicit doesn't mean "doesn't exist if you try to convert"; it means "error if you try to convert". The point of explicit is to force the user to think about what type they actually want to use. It's there to prevent a user from writing code that is somewhat ambiguous to the reader.
I believe clang is correct here. Overload resolution in C++ works in three phases: First a set of candidate functions is constructed, which is the set of all functions that the call may potentially refer to (basically, the set of all functions that name resolution picks up). This initial set of candidate functions is then narrowed down to arrive at a set of viable functions (the set of functions that could take a call with the given arguments). Finally, the viable functions are ranked to determine the best viable function. This best viable function is what ultimately will be called.
From [over.match.viable]/4
Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F. […]
Based on [over.best.ics]/6, particularly
When the parameter type is not a reference, the implicit conversion sequence models a copy-initialization of the parameter from the argument expression. […]
it would seem that there is no such implicit conversion sequence for void func(A a) due to the necessary constructor being marked explicit (copy-initialization would fail). Therefore, the function is not a viable function and is not considered anymore for overload resolution, which leaves void func(const vector<int>& a) as the only viable candidate, which is the function that will then be called.
Also, purely on a conceptional level, it would seem to make sense that the copy-list-initialization of a parameter can only be ill-formed once we actually know which parameter we're going to initialize, i.e., know which function is actually going to be called. If a call to an overload set would be illegal the moment there is a single argument that is not a valid initializer for the corresponding parameter in every single potential candidate function, then what's the point of overloading? As long as we're still working on figuring out which function to call, there is no way to decide whether the initialization would be ill-formed or not. clang exhibits exactly this behavior. When you comment out the void func(const std::vector<int>& a) overload, clang will suddenly complain that the call is ill-formed…
try it out here
I agree with #Nicol Bolas. MSVC and gcc are right while clang and icc are wrong.
In overloading resolution, list initialization differs from copy initialization that list initialization considers explicit constructors while copy initialization doesn't.
(From cppreference)
List-initialization When an object of non-aggregate class type T is
list-initialized, two-phase overload resolution takes place.
at phase 1, the candidate functions are all initializer-list
constructors of T and the argument list for the purpose of overload
resolution consists of a single initializer list argument if overload
resolution fails at phase 1, phase 2 is entered, where the candidate
functions are all constructors of T and the argument list for the
purpose of overload resolution consists of the individual elements of
the initializer list. If the initializer list is empty and T has a
default constructor, phase 1 is skipped.
In copy-list-initialization, if phase 2 selects an explicit
constructor, the initialization is ill-formed (as opposed to all over
copy-initializations where explicit constructors are not even
considered).
Some examples:
This one
#include <iostream>
#include <initializer_list>
struct A
{
explicit A(int, int, int) {}
};
struct B
{
B(std::initializer_list<int>) {}
};
void f(A) //f1
{
std::cout << 1 << std::endl;
}
void f(B) //f2
{
std::cout << 2 << std::endl;
}
int main()
{
f({ 1,2,3 }); //list initialziation
}
fails on MSVC and gcc. (See here and here)
This one
#include <iostream>
#include <initializer_list>
struct A
{
explicit A(std::initializer_list<int>) {}
};
struct B
{
B(std::initializer_list<int>) {}
};
void f(A) //f1
{
std::cout << 1 << std::endl;
}
void f(B) //f2
{
std::cout << 2 << std::endl;
}
int main()
{
f({ 1,2,3 }); //Also list initialization
}
also fails on MSVC and gcc. (See here and here)
While this one
#include <iostream>
#include <initializer_list>
struct A
{
explicit A(int) {}
};
struct B
{
B(int) {}
};
void f(A) //f1
{
std::cout << 1 << std::endl;
}
void f(B) //f2
{
std::cout << 2 << std::endl;
}
int main()
{
f(1); //Copy initialization
}
successes on all four compilers.
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. :-)