#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.
Related
In code like this:
#include <iostream>
#include <initializer_list>
#include <string>
struct A
{
A() { std::cout << "2" << std::endl; }
A(int a) { std::cout << "0" << std::endl; }
A(std::initializer_list<std::string> s) { std::cout << "3" << std::endl; }
A(std::initializer_list<int> l) { std::cout << "1" << std::endl; }
};
int main()
{
A a1{{}};
}
Why does it call std::initializer_list<int> specification of constructor?
It'll generate ambiguity compilation error if we define, for example, constructor with std::initializer_list<double>. What are the rules of such construction and why is it so specific about std::initializer_list with number as template argument?
If a class has an initializer list constructor, then {whatever goes here} means to pass {whatevergoeshere} as argument to the present constructors (if there are no initializer list constructors, then whatever goes here are passed as arguments).
So let's simplify the setting and ignore the other constructors, because apparently the compilers don't care about them
void f(std::initializer_list<std::string> s);
void f(std::initializer_list<int> l);
For f({{}}) we have this rule
Otherwise, if the parameter type is std::initializer_list and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.
Here we have a single element {} and it needs a user defined conversion to initialize std::string and no conversion (identity) for int. Therefore, int is chosen.
For f({{{}}}) the element is {{}}. Can it be converted to int? The rule is
if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type
...
In all cases other than those enumerated above, no conversion is possible.
Can it be converted to std::string? Yes, because it has an initializer list constructor that has a std::initializer_list<char> init parameter. Therefore, std::string is chosen this time.
The difference to A a3({}) is that in such a case, it's not list initialization, but a "normal" initialization with a {} argument (note that one less nesting because of the missing outer braces). Here our two f-functions are called with {}. And since both lists have no elements, for both we have identity conversions and therefore an ambiguity.
The compiler in this case will also consider f(int) and get a tie with the other two functions. But a tie-breaker would apply that declares the int -parameter worse than the initializer_list parameters. So you have a partial order {int} < {initializer_list<string>, initializer_list<int>}, which is the reason for ambiguity, as the best group of conversion sequences does not contain a single candidate, but two.
{} to a scalar type (such as int, double, char*, etc.) is the identity conversion.
{} to a class type other than a specialization of std::initializer_list (e.g., std::string) is a user-defined conversion.
The former beats the latter.
Consider the code below:
#include <iostream>
#include <vector>
void f(std::vector<int> v) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
void f(int n) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
int main()
{
f({42}); // the int overload is being picked up
}
Live on Coliru
I was a bit surprised to realize that in this case the int overload is being picked up, i.e. the output of the program is:
void f(int)
with the warning
warning: braces around scalar initializer [-Wbraced-scalar-init] f({42});
Of course this happens only when I pass a 1-element list as an argument, otherwise the std::vector overload is being picked up.
Why is {42} treated like a scalar and not like a init-list? Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
PS: The std::vector has an init-list constructor
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());
see (7) from cppreference.
Braced initializer has no type, we can't say {42} is an int or std::initializer_list<int>. When it's used as an argument, special rules for overload resolution will be applied for overloaded function call.
(emphasis mine)
Otherwise, if the parameter type is not a class and the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type
{42} has only one element with type int, then it's exact match for the overload void f(int). While for void f(std::vector<int>) a user-defined conversion is needed. So void f(int) will be picked up here.
Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
As a wordaround, you can put additional braces to force the compiler construct a std::initializer_list<int> and then pick up void f(std::vector<int>):
f({{42}});
LIVE
Forcing std::vector overload
int main()
{
f(std::vector<int>{42}); // the vector overload is being picked up now
}
Why isn't the vector(initializer_list) constructor being picked up?
Assume that another header declares a void f(std::set<int> v).
How would you like the compiler to react when faced with f({1}): construct a vector or construct a set?
Consider the code below:
#include <iostream>
#include <vector>
void f(std::vector<int> v) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
void f(int n) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
int main()
{
f({42}); // the int overload is being picked up
}
Live on Coliru
I was a bit surprised to realize that in this case the int overload is being picked up, i.e. the output of the program is:
void f(int)
with the warning
warning: braces around scalar initializer [-Wbraced-scalar-init] f({42});
Of course this happens only when I pass a 1-element list as an argument, otherwise the std::vector overload is being picked up.
Why is {42} treated like a scalar and not like a init-list? Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
PS: The std::vector has an init-list constructor
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());
see (7) from cppreference.
Braced initializer has no type, we can't say {42} is an int or std::initializer_list<int>. When it's used as an argument, special rules for overload resolution will be applied for overloaded function call.
(emphasis mine)
Otherwise, if the parameter type is not a class and the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type
{42} has only one element with type int, then it's exact match for the overload void f(int). While for void f(std::vector<int>) a user-defined conversion is needed. So void f(int) will be picked up here.
Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
As a wordaround, you can put additional braces to force the compiler construct a std::initializer_list<int> and then pick up void f(std::vector<int>):
f({{42}});
LIVE
Forcing std::vector overload
int main()
{
f(std::vector<int>{42}); // the vector overload is being picked up now
}
Why isn't the vector(initializer_list) constructor being picked up?
Assume that another header declares a void f(std::set<int> v).
How would you like the compiler to react when faced with f({1}): construct a vector or construct a set?
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. :-)
#include <initializer_list>
#include <vector>
struct test
{
using t = std::vector<test>;
test(t const &v)
{
}
test(t &&v)
{
}
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
};
Both Clang and GCC complain that the third constructor, the one taking the initializer list, delegates to itself. I don't understand how this is possible though, because you can't construct an initializer list from a vector.
It is trivial to fix the error by replacing the outer curly braces with round parenthesis, but why would this be an issue in the first place? This almost identical program compiles just fine:
#include <initializer_list>
struct a {};
struct b {};
struct test
{
test(a const &)
{
}
test(a &&)
{
}
test(std::initializer_list<b> v)
: test{a{}} //no error, still using curly braces
{
}
};
Interestingly, with the above second example, the error reappears if you substitute b with test. Can someone explain what is going on here?
The type of t{v} is std::vector<test>. The idea is that init-list constructors are always preferred wrt any other constructors, so test{t{v}} will first try to call an init-list constructor, if one exists, and if the types are compatible. In your case, this is possible, since test itself can be implicitly constructed from a std::vector<test> (via your first 2 constructors), so the compiler ends up delegating to the init-list constructor, hence the error.
In the second case, there is no ambiguity, since the type a{}is not implicitly convertible anymore to std::initializer_list<b>.
Make the constructors explicit in the first example, or call the base constructor with test(t{v}) instead, and your ambiguity will disappear (the compiler won't perform the implicit conversion anymore).
A simpler example (live here) that exhibits essentially the same behaviour as your first example is:
#include <initializer_list>
struct test
{
/*explicit*/ test(int){} // uncomment explicit and no more errors
test( std::initializer_list<test> v)
: test{42} {} // error, implicitly converts 42 to test(42) via the test(int)
};
int main(){}
The relevant part of the standard that deals with init-list constructors is §13.3.1.7/1 [over.match.list] - citation below taken from a now-deleted answer of #Praetorian -
When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, 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.
I'm no C++ expert but the main difference is that
test{a{}}
Does not have an overload for initializer_list<a> so that constructor is not available.
On the other hand test{t{v}} does have an initializer_list<test> constructor available to it because you can create a test from vector. It can use the (I don't know the name of the rule) 1 cast transformation.
test{t{v}} -> test{test(t{v})}
The problem in your code is that you're writing a function calling itself.
test(std::initializer_list<test> v)
: test{t{v}} //error
{
}
test{t{v}} will first call the initializer-list of vector<test> with v as parameter. But vector will call the function again to initialize the value which will fail.
The compiler doesn't know how to resolve the problem. Changing the initialization to use parentheses will (as you said) fix this because it will then call an implicit copy constructor in the vector (doing nothing because the your structure isn't doing anything).
The second example starts with this call: a{}
It is resolved fine because a is a basic structure with implicitly defined constructors. The second call is the test{...} one
Because the type is a and there is a constructor for a it runs just fine.