22

I really don't understand this, I thought that compiler first executes what is in braces and then gives the result to the most appropriate function. Here it looks like it gives the function an initializer list to deal with it...

#include <string>
#include <vector>
using namespace std;

void func(vector<string> v) { }

void func(vector<wstring> v) { }

int main() {
  func({"apple", "banana"});
}

error:

<stdin>: In function 'int main()':
<stdin>:11:27: error: call of overloaded 'func(<brace-enclosed initializer list>)' is ambiguous
<stdin>:11:27: note: candidates are:
<stdin>:6:6: note: void func(std::vector<std::basic_string<char> >)
<stdin>:8:6: note: void func(std::vector<std::basic_string<wchar_t> >)

Why isn't my func(vector<string> v) overload called, and can I make it so?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
rsk82
  • 28,217
  • 50
  • 150
  • 240

1 Answers1

25

This one was subtle.

std::vector has a constructor taking two range iterators. It is a template constructor (defined in 23.6.6.2 of the C++11 Standard):

template<typename InputIterator>
vector(InputIterator first, InputIterator last, 
const allocator_type& a = allocator_type());

Now the constuctor of std::vector<wstring> accepting an initializer_list is not a match for the implicit conversion in your function call, (const char* and string are different types); but the one above, which is of course included both in std::vector<string> and in std::vector<wstring>, is a potentially perfect match, because InputIterator can be deduced to be const char*. Unless some SFINAE technique is used to check whether the deduced template argument does indeed satisfy the InputIterator concept for the vector's underlying type, which is not our case, this constructor is viable.

But then again, both std::vector<string> and std::vector<wstring> have a viable constructor which realizes the conversion from the braced initializer list: hence, the ambiguity.

So the problem is in the fact that although "apple" and "banana" are not really iterators(*), they end up being seen as such. Adding one argument "joe" to the function call fixes the problem by disambiguating the call, because that forces the compiler to rule out the range-based constructors and choose the only viable conversion (initializer_list<wstring> is not viable because const char* cannot be converted to wstring).


*Actually, they are pointers to const char, so they could even be seen as constant iterators for characters, but definitely not for strings, as our template constructor is willing to think.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 2
    Now I hate so-called "uniform initialization" even more now, it's interfered with the feature I liked, `std::initializer_list`. – Benjamin Lindley Jan 29 '13 at 17:45
  • @BenjaminLindley: Indeed that's an unfortunate interference. I am still wondering whether the use of the name `InputIterator` for the template parameter in the Standard means that the compiler should check whether the template parameter satisfies the `InputIterator` concept. I *think* that's not the case, but I'm not 100% sure. – Andy Prowl Jan 29 '13 at 17:50
  • @Andy But it does satisfy the `InputIterator` concept (and more) doesn't it? The string literals are implicitly converted to `char const *` and those are `RandomAccessIterator`s. – Praetorian Jan 29 '13 at 17:59
  • 1
    @Praetorian: You're right, it does, but as constant iterator of `char`s, not of `string`s or `wstring`s. – Andy Prowl Jan 29 '13 at 18:00
  • it takes the other ctor because the init list ctor is not only a non-perfect match, but no match at all :) it will not accept c-strings, but only wc-strings, as you point out later in your answer. unfortunately, not even making the ctor `explicit` will fix the problem :) Overload resolution will still consider it and the call is still ambiguous. – Johannes Schaub - litb Jan 30 '13 at 21:53
  • @JohannesSchaub-litb: For `vector` yes, you're right: it is not a match at all. But for `vector` it is a match, just not a perfect one: it requires a conversion, while the template version does not require any conversion and that's why it's preferred. But both classes have the template version, and that's what leads to ambiguity. Or am I mistaken? – Andy Prowl Jan 30 '13 at 22:44
  • 1
    @AndyProwl your description makes it sounds to me "this constructor is a better match than the initializer list constructor, so it is preferred". But even if the template constructor would be a better match if the constructors would be compared against each other, the compiler would still use the initializer list constructor. Because the compiler first *only* looks at the initializer list constructors. And only if they don't match at all, then it considers all other constructors in addition, this time taking the initializer list elements as arguments separately, instead of the whole list. – Johannes Schaub - litb Jan 30 '13 at 22:50
  • @JohannesSchaub-litb: You managed to turn my certainties back into confusion - but I guess that's what leads to real knowledge :-) If the compiler looks at initializer list constructors first, why does it not pick `vector(initializer-list)`, which is a viable one? – Andy Prowl Jan 30 '13 at 22:53
  • @AndyProwl it does pick it – Johannes Schaub - litb Jan 30 '13 at 22:54
  • @JohannesSchaub-litb: [scratching head] So why the ambiguity? – Andy Prowl Jan 30 '13 at 22:54
  • 2
    @AndyProwl because for `vector`, it picks the template constructor and then both `func`s match, with no one being better than the other. If one of the `func`s would have its parameter of type `std::initializer_list` and the other would not, that `func` would be preferred. It does not matter than in a nested overload resolution context, one parameter of the functions use an initializer list constructor to initialize itself. What matters is the parameter type itself, which is in both cases `vector`. – Johannes Schaub - litb Jan 30 '13 at 22:59
  • @JohannesSchaub-litb: I think I'm starting to understand, thank you for explaining. So when comparing the conversion sequences from the `initializer_list` to `std::vector` and `std::vector`, both sequences are equally good, regardless of the constructors which are invoked to realize the conversions. Did I get it right? (fingers crossed) – Andy Prowl Jan 30 '13 at 23:05
  • There is no `initializer_list`. A `{ "apple", "banana" }` is a braced initializer list. It can be converted to a `struct A { char const*a, *b; };` just as well as to `char const *x[2];`. And when initializing a `std::vector`, it is converted (in the nested overload resolution of the initializer list constructors) to `std::initializer_list`. – Johannes Schaub - litb Jan 30 '13 at 23:11
  • @AndyProwl but I think you get this right now, apart from the confusion of introducing that `initializer_list` :) Two user defined conversion sequences can only be compared if both sequences use the same constructor or conversion function (C++11 added some other case involving aggregate initialization, which however doesn't matter in this case). In this case, the constructors are members of different classes, so that will hardly work :) – Johannes Schaub - litb Jan 30 '13 at 23:12
  • @JohannesSchaub-litb: Thank you, that's a great lesson for me. Hope I won't make this reasoning mistake again. – Andy Prowl Jan 30 '13 at 23:14
  • For example in `struct A { operator int(); } void f(int); void f(long);` if you call it with `f(A());`, then the two user defined conversion sequences `A->int` and `A->long` use the same conversion function. They can be compared (the conversion sequence of them converting from the return type of the conversion function to the target type of the sequence is compared). And for the first it is better than for the second (identity against integral conversion). Hence for this, the first `f` wins. – Johannes Schaub - litb Jan 30 '13 at 23:22
  • @JohannesSchaub-litb: I see. So here we have two conversion sequences, one from `A` to `int` and one from `A` to `long`, but the first one is preferred over the other one because it requires just one user-defined conversion, while the second one requires a user-defined conversion and a built-in conversion. While in the example by the OP we had two equally good conversion sequences consisting of one user-defined conversion, and it doesn't matter if those conversions are achieved through a constructor accepting an `initializer_list` or through a template construction. Am I finally getting it? – Andy Prowl Jan 30 '13 at 23:41
  • @AndyProwl yes that's it :) – Johannes Schaub - litb Feb 02 '13 at 15:18