c++overloadingambiguous-call

How to resolve ambiguity between constructors taking std::string and std::vector


I want to make a "Tag" class that can have its name specified either as a dot separated name like "this.is.my.name" or as a vector of strings, like {"this","is","my","name"}.

When I try to do this, I am sometimes getting told by the compiler that my calls are ambiguous. I want to know (1) why this is ambiguous at all, and (2) why it is only ambiguous sometimes.

Here is my example code, which you can also view and compile here on Coliru

#include <string>
#include <vector>
#include <iostream>

class Tag
{
public:
    explicit Tag(std::string name);
    explicit Tag(std::vector<std::string> name);

};

Tag::Tag(std::string name)
{
    //here 'name' will be a dotted collection of strings, like "a.b.c"
}

Tag::Tag(std::vector<std::string> name)
{
    //here 'name' will be a vector of strings, like {"a","b","c"}
}


int main(int argc, char**argv)
{
    Tag imaTag{{"dotted","string","again"}};
    Tag imaTagToo{"dotted.string"};

    //everything is fine without this line:
    Tag imaTagAlso{{"dotted","string"}};
    std::cout << "I made two tags" << std::endl;

}

With the indicated line, I get the following error:

g++ -std=c++11 -O2 -Wall -pthread main.cpp && ./a.out
main.cpp: In function 'int main(int, char**)':
main.cpp:28:39: error: call of overloaded 'Tag(<brace-enclosed initializer list>)' is ambiguous
     Tag imaTagAlso{{"dotted","string"}};
                                   ^
main.cpp:18:1: note: candidate:     'Tag::Tag(std::vector<std::__cxx11::basic_string<char> >)'
 Tag::Tag(std::vector<std::string> name)
 ^~~
main.cpp:13:1: note: candidate: 'Tag::Tag(std::__cxx11::string)'
 Tag::Tag(std::string name)
 ^~~

Solution

  • Tag imaTagAlso{{"dotted","string"}}; says construct a Tag, call it imaTagAlso and initialize it with {"dotted","string"}. The issue with that is std::string can be constructed by a pair of iterators and since string literals can decay to const char*'s, they qualify as iterators. So you could either call the string constructor using the "iterators", or you could call the vector constructor using its std::initializer_list constructor. To work around this you can use

    Tag imaTagAlso{{{"dotted"},{"string"}}};
    

    which says construct a Tag, call it imaTagAlso and initialize it with {{"dotted"},{"string"}} and now {"dotted"} and {"string"} become elements of the std::initializer_list for the vector constructor.

    You could also (since c++14) use std::string's user defined literal operator (""s) like

    Tag imaTagAlso{{"dotted"s,"string"s}};
    

    which makes each element of the braced-init-list std::string's, and the vector constructor will be chosen.