Related question:
The question is similar to 1 and 2, but not the same.
#include <type_traits>
#include <vector>
struct A {
A();
};
static_assert(std::is_convertible_v<double, A> == false);
static_assert(std::is_convertible_v<A, double> == false);
void func(std::vector<double> values);
void func(std::vector<A> as);
int main() {
func({ 4.2 });
}
Apparently, double and A cannot be implicitly converted to each other. So I think void func(std::vector<double>) should be called.
But the results for different compilers are different: https://godbolt.org/z/c1hW47f4c
GCC fails to compile with:
<source>: In function 'int main()':
<source>:14:9: error: call of overloaded 'func(<brace-enclosed initializer list>)' is ambiguous
14 | func({ 4.2 });
| ~~~~^~~~~~~~~
<source>:10:6: note: candidate: 'void func(std::vector<double>)'
10 | void func(std::vector<double> values);
| ^~~~
<source>:11:6: note: candidate: 'void func(std::vector<A>)'
11 | void func(std::vector<A> as);
| ^~~~
Compiler returned: 1
(VC15 (VS 2017) rejects the example too.)
Which one is right? Why?
tldr;
The program is ill-formed because during the overload resolution of the call
func({4.2})for the second overloadfunc(std::vector<A> as)the explicit ctor ofstd::vectorthat takes asize_tis choosen which isn't allowed in copy-list-initialization.Language-Lawyer Explanation
Step 1
First let us consider the overload resolution for the call
func({4.2})against the first overloadfunc(std::vector<double>).Note note that
func({ 4.2 })is copy-initialization:Now we move onto dcl.init.general#16 to see that this will use list initialization:
So from dcl.init.list we also see that this is copy-list-initialization:
Finally we move onto dcl.init.list#3:
This means that overload resolution is done with
std::vector's for the argument{4.2}and the best one will be choosen.So we move onto over.match.list:
Note that since an initializer list ctor was found, so overload resolution won't be performed again. This in turn means that the initializer list ctor is the choosen option when matching
func({4.2})against the first overloadfunc(std::vector<double>)Step 2
Now we see how
func({4.2})matches against the second overloadfunc(std::vector<A>).In this case, almost all the steps are same(as in the last case) except that this time the initializer list ctor
std::vector(std::initializer_list<A>)is not viable and so the statement if no viable initializer-list constructor is found, overload resolution is performed again is satisfied and soThis means that this time, the
std::size_targument ctor ofstd::vectorwill be choosen. But note that this ctor ofstd::vectorisexplicitand we have:Thus the selection of
size_targument ctor ofstd::vectormakes the program ill-formed.