ghchoi
ghchoi

Reputation: 5156

call of overloaded ‘<brace-enclosed initializer list>’ with suffices for pairs is ambiguous

I cannot compile the following code.

void print_number(long n) {
   std::cout << n << std::endl;
}

void print_number(float n) {
   std::cout << n << std::endl;
}

void print_pair(std::pair<std::string, long> p) {
   std::cout << std::get<1>(p) << std::endl;
}

void print_pair(std::pair<std::string, float> p) {
   std::cout << std::get<1>(p) << std::endl;
}

int main() {
   print_number(12l);
   print_number(3.4f);

   print_pair({"long", 12l});
   print_pair({"float", 3.4f});

   return 0;
}

print_number functions work well. However, compiler complains about the overloaded print_pair functions: error: call of overloaded ‘print_pair(<brace-enclosed initializer list>)’ is ambiguous.

Are suffices in <brace-enclosed initializer list> or std::pair not working? How can I overload functions receiving a std::pair parameter?

Upvotes: 5

Views: 1060

Answers (4)

ghchoi
ghchoi

Reputation: 5156

Thanks for all answers. I up-voted all of them, however, what I wanted to do was call the function print_pair with heterogeneous <brace-enclosed initializer list>s.

What I finally did is:

struct number {
    number(long n) {
        std::cout << n << std::endl;
    }
    number(float n) {
        std::cout << n << std::endl;
    }
};

void print_pair(std::pair<std::string, number> p) {}

In this way, the following function calls can be compiled without the error: call of overloaded ‘print_pair(<brace-enclosed initializer list>)’ is ambiguous.

print_pair({"long", 12l});
print_pair({"float", 3.4f});

Note that, using two structs whose constructors take long and float respectively, we will go back to the same problem of ambiguity.

Upvotes: 0

Barry
Barry

Reputation: 302758

C++'s overload resolution logic doesn't consider piece-wise conversions, it only considers the whole conversion for a given parameter/argument pair.

With this call:

print_pair({"long", 12l});

We have two viable candidates. One candidate takes a std::pair<std::string, long> and the other takes a std::pair<std::string, float>.

What C++ doesn't do (and what you might be thinking it does) is consider these parts separately - and say that well, "long" to std::string is the same in both cases but 12l to long is better than 12l to float, as a result the conversion to pair<string, long> wins. This would be how it would work if instead of having two functions that take pairs, we had two functions that took two arguments:

print_two("long", 12l); // calls print_two(string, long)

But C++ doesn't work like that. What it does do is consider these parts together. That braced-init-list can be used to construct both std::pair specializations. Both are viable. Both are user-defined conversions. Neither is better than the other, hence ambiguous. We really have no mechanism in the language to make this kind of piecewise conversion work.

You have to either just say what you mean:

print_pair(std::pair<std::string, long>("long", 12l));

Or take two arguments.

Upvotes: 2

Jarod42
Jarod42

Reputation: 217135

With print_pair({"long", 12l});, there are 2 possible overloads:

  • void print_pair(std::pair<std::string, long>)
  • void print_pair(std::pair<std::string, float>)

{"long", 12l} has no types, but is valid initialization for both std::pair<std::string, long> and std::pair<std::string, long>.

So the call is ambiguous.

print_pair(std::pair{"long", 12l}); would also be ambiguous
as std::pair<const char*, long> can equally be converted to std::pair<std::string, long> and std::pair<std::string, long>.

You have to call it:

  • print_pair(std::pair{std::string("long"), 12l}); to have exact match.

Upvotes: 2

BiagioF
BiagioF

Reputation: 9705

Tl;dr

In order to solve the ambiguity, you can provide the correct type to the compiler:

print_pair(std::make_pair<std::string, long>("long", 12l));
print_pair(std::make_pair<std::string, float>("float", 3.4f));

or

print_pair(std::make_pair(std::string("long"), 12l));
print_pair(std::make_pair(std::string("float"), 3.4f));

The ambiguity problem arises from the fact "long" and "float" are not std::string, but rather const char[].

So when you try to construct a std::pair using the following expression: std::pair{"long", 12l}, what you obtain is a std::pair<const char[5], long>.

(The same for the float; i.e., std::pair<const char[5], float>).

Your overload print_pair accepts std::pair<std::string, long> or std::pair<std::string, float>. None of the previous types match, so the compiler has to perform a conversion. Because of that, it cannot automatically deduct what conversion you want to perform. Both are valid.

For example:

                    std::pair<const char[5], long>  
                                  |
  ----------------------------------------------------------------
  v                                                              v
std::pair<std::string, long>                          std::pair<std::string, float>

To "prove" the problem is the std::string (neither long or float), you can also solve the ambiguity constructing a proper std::string (instead of an array of char):

print_pair(std::make_pair(std::string("long"), 12l));
print_pair(std::make_pair(std::string("float"), 3.4f));

Starting from C++17, this boilerplate can be simplified with:

using namespace std::string_literals;

print_pair(std::pair{"long"s, 12l});
print_pair(std::pair{"float"s, 3.4f});

Upvotes: 3

Related Questions