60

Why are the first two calls to doSomething OK by the compiler, but using two elements in the list causes an ambiguous call?

#include <vector>
#include <string>

void doSomething(const std::vector<std::string>& data) {}

void doSomething(const std::vector<int>& data) {}

int main(int argc, char *argv[])
{
    doSomething({"hello"}); // OK
    doSomething({"hello", "stack", "overflow"}); // OK
    doSomething({"hello", "stack"}); // C2668 'doSomething': ambiguous call

    return 0;
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
koolbanana
  • 719
  • 5
  • 11

4 Answers4

62

What is happening here is that in the two element initializer list both of the string literals can be implicitly converted to const char* since their type is const char[N]. Now std::vector has a constructor that takes two iterators which the pointers qualify for. Because of that the initializer_list constructor of the std::vector<std::string> is conflicting with the iterator range constructor of std::vector<int>.

If we change the code to instead be

doSomething({"hello"s, "stack"s});

Then the elements of the initializer list are now std::strings so there is no ambiguity.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 6
    Or `doSomething(std::vector({"hello", "stack"}));`, but your solution is more readable. In any case, if this is in real code, I'd probably also add a small comment to explain the seemingly useless explicitness. – Christian Hackl Jan 06 '17 at 14:58
  • I thought the initializer_list constructor is always called if possible, for example in std::vector{5,1} one gets a vector with elements 5 and 1 and not 5 times 1. Why is that not the case here? – ab.o2c Jan 11 '17 at 08:41
  • @ab.o2c The initializer list constructor is preferred but we do not have `std::vector{5,1}` in this case. What we have is `std::vector{const char*, const char*}`. Since the initializer list has to have the same type as the elements of the vector it is ignored in this case and the iterator constructor is chosen. – NathanOliver Jan 11 '17 at 19:07
  • @NathanOliver You are right, I got the answer wrong. I thought you said, that the iterator constructors of both vectors were conflicting, but that was not your answer :-) I needed some testing to get that point. +1 now for you. – ab.o2c Jan 12 '17 at 09:18
  • Like the solution idea using the `string operator " " s` – clickMe Jan 29 '17 at 18:36
  • Possible candidate/nominee for "Second Most Vexing Parse?" – Adrian Mole Oct 08 '19 at 14:06
23

Both the one-argument and three-argument lists can only match std::vector<std::string>'s std::initializer_list constructor. However, the two-argument list matches one of the constructors from std::vector<int>:

template <class InputIt>
vector(InputIt first, InputIt last, Allocator const &alloc = Allocator());

Indeed, a char const * can be incremented, and dereferenced to get a char that is implicitly convertible to an int.

Quentin
  • 62,093
  • 7
  • 131
  • 191
17

"hello" and "stack" both decay to const char * which satisfies the InputIterator concept. This allow's them to match std::vector's constructor #4.

If you pass std::string objects the ambiguity is resolved.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87
0

may be you can overload the doSomething() with initializer_list parameter, like this:

void doSomething(initializer_list<string> l) {
    doSomething(vector<string>(l));
}

then, doSomething({"hello", "stack"}); will call the function you desired.

neolc
  • 1
  • 1
  • 3
    "i'm not sure if this works" - please only provide answers if you know they work. Other answers explain why the problem occurs and therefore convey much more information, leaving the solution up to the user to determine the best course of action. – simo.3792 May 27 '21 at 06:04
  • thanks for the reminder,i test it in linux/clang++/c++11,works fine. – neolc Jun 02 '21 at 00:42