Search code examples
c++stringc++11stdvectorambiguous-call

Disambiguating list initialization for std::vector<std::string>


I have an overloaded function in my code with the type signatures:

void foo(std::string);
void foo(std::vector<std::string>);

I would like the user of foo to be able to call it with either a string or a list of strings

//Use case 1
foo("str");

//Use case 2
foo({"str1","str2","str3"});
foo({"str1","str2","str3","str4"});

The problem is when the caller passes in two strings into the initializer list for foo.

//Problem!
foo({"str1","str2"});

This call to foo is ambiguous because it matches both type signatures. This is because apparently {"str1","str2"} is a valid constructor for std::string

So my question is is there anything I can do in the declaration or implementation of foo such that I maintain the API I described above without hitting this ambiguous constructor case.

I do not want to define my own string class, but I am okay with defining something else instead of vector<string> as long is it can be initialized with an initializer list of strings.

Only out of curiosity, why does the string constructor accept {"str1","str2"}?


Solution

  • {"str1","str2"} matches the std::string constructor that accepts two iterators. Constructor 6 here. It would try to iterate from the beginning of "str1" to just before the beginning of "str2" which is undefined behavior.

    You can solve this ambiguity by introducing an overload for std::initializer_list<const char*> which forwards to the std::vector<std::string> overload.

    void foo(std::string);
    void foo(std::vector<std::string>);
    
    void foo(std::initializer_list<const char*> p_list)
    {
        foo(std::vector<std::string>(p_list.begin(), p_list.end()));
    }