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)
^~~
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.