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.
Related
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.
#include <vector>
using std::size_t;
struct Foo
{
Foo(size_t i, char c) {}
};
Foo Bar1()
{
size_t i = 0;
char c = 'x';
return { i, c }; // good
}
std::vector<char> Bar2()
{
size_t i = 0;
char c = 'x';
return { i, c }; // bad
}
https://wandbox.org/permlink/87uD1ikpMkThPTaw
warning: narrowing conversion of 'i' from 'std::size_t {aka long
unsigned int}' to 'char' inside { }
Obviously it tries to use the initializer_list of vector. But why doesn't it use the better match vector<char>(size_t, char) ?
Can i use the desired constructor in a return statement without writing the type again?
Because initializer_list constructors, if at all possible, take precedence over other constructors. This is to make edge cases less confusing - specifically, this particular vector constructor that you expect it to use was deemed too easily selected by accident.
Specifically, the standard says in 16.3.1.7 "Initialization by list-initialization" [over.match.list] (latest draft, N4687):
(1) When objects of non-aggregate class type T are list-initialized such that 11.6.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 (11.6.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.
So if you do std::vector<char>( i, c ), this section does not apply at all, since it isn't list-initialization. Normal overload resolution is applied, the (size_t, char) constructor is found, and used.
But if you do std::vector<char>{ i, c }, this is list-initialization. The initializer list constructors are tried first, and the (initializer_list<char>) constructor is a match (even though it involves the narrowing conversion from size_t to char), so it is used before the size+value constructor is ever considered.
So to answer the edited-in question: no, you can't create the vector without naming its type. But in C++17 you can use class template argument deduction and simply write return std::vector(i, c);
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?