Search code examples
c++compiler-errorsoverload-resolutionconstructor-overloading

Overloaded call is ambiguous: one-pair inline map as constructor argument


I have a class––roughly similar to the one below––that takes a map as the only argument to its only constructor.

#include <iostream>
#include <map>

using namespace std;

class Dict {
    public:
    Dict (map<int, int> contents) {
        elements = contents;
    }

    int getElement (int i) {
        return elements[i];
    }

    map<int, int> elements;
};

int main() {
    Dict* test0 = new Dict({{1, 2}, {3, 4}});    /* Succeeds */
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
}

As mentioned in the comments above, the first constructor doesn't throw an error; it's consistent with answers such as this. The ambiguous call error is as follows:

main.cpp:43:36: error: call of overloaded 'Dict()' is ambiguous
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
                                ^
main.cpp:16:5: note: candidate: Dict::Dict(std::map)
     Dict (map<int, int> contents) {
     ^
main.cpp:14:7: note: candidate: Dict::Dict(const Dict&)
 class Dict {
       ^
main.cpp:14:7: note: candidate: Dict::Dict(Dict&&)

If the keys and values in the map are of different types (for instance, if Dict() takes a map of ints to booleans and I call new Dict({{1, true}})), this error doesn't arise and the code works as expected.

How is this single constructor ambiguous? Why is it ambiguous specifically in the case where there is one mapping between two objects of the same type? Are there any obvious work-arounds in vanilla C++?


Solution

  • This is caused primarily by this constructor of std::map:

    template< class InputIterator >
    map( InputIterator first, InputIterator last,
         const Compare& comp = Compare(),
         const Allocator& alloc = Allocator() );
    

    Even if the arguments are not iterators, this constructor is enabled, thus participates overload resolution. As a result,

    {1, 2}   -> std::map<int, int>
    {{1, 2}} -> std::map<int, int>    
    

    are both valid conversions, which means

    {{1, 2}} -> Dict
    {{1, 2}} -> std::map<int, int> 
    

    are both valid conversions. Therefore, the three constructors of Dict are ambiguous:

    Dict(map<int, int>);
    Dict(const Dict&);
    Dict(Dict&&);
    

    For the case of new Dict({{1, true}}), InputIterator cannot be deduced correctly, thus there is no ambiguity any more.

    You can make Dict(map<int, int>); explicit, or use three pairs of braces suggested by Ben Voigt.


    Why do three pairs of braces work?

    Because in this case, for the copy/move constructor candidate, user-defined conversions are not allowed. This is explicitly stated in [over.best.ics]/4 (unrelated parts are elided by me):

    However, if the target is

    • the first parameter of a constructor or

    • ...

    and the constructor or user-defined conversion function is a candidate by

    • ... or

    • the second phase of [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to cv X,

    user-defined conversion sequences are not considered.