C++ Constructor is ambiguous with std::map of the same key/value types

This is a fun one.

A map can be constructed from two iterators:

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

Notably, this constructor is not required to check that InputIterator is an iterator at all, let alone that the result of dereferencing it is convertible to the map's value type. Actually trying to construct the map will fail, of course, but to overload resolution, map is constructible from any two arguments of the same type.

So with

Collection<std::string> col({
  { "key", "value" }
});

The compiler sees two interpretations:

  • outer braces initializes a map using the map's initializer-list constructor, inner braces initializes a pair for that initializer-list constructor.
  • outer braces initializes a Collection, inner braces initializes a map using the "iterator-pair" constructor.

Both are user-defined conversions in the ranking, there is no tiebreaker between the two, so the call is ambiguous - even though the second, if chosen, would result in an error somewhere inside map's constructor.

When you use braces on the outermost layer as well:

Collection<std::string> col{{
  { "key", "value" }
}};

There is a special rule in the standard that precludes the second interpretation.


In this case, you are missing a {} that encloses the map {{ "key", "value" }}

EDIT: Sorry I can't comment on T.C's answer because of insufficient reputation. In any case, thanks for brilliantly highlighting the point of ambiguity.

I wanted to add on to their answer - to give a complete picture of why constructing with {} does not result in this ambiguity but constructing with () does.

The key difference between braced and parentheses initialization is that during constructor overload resolution, braced initializers are matched to std::initializer_list parameters if at all possible, even if other constructors offer better matches. This is why constructing with {} can resolve the ambiguity.

(This is taken from Item 7 of Scott Myers' Effective Modern C++)

Tags:

C++