I ran into a real-life WTF moment when I discovered that the code below outputs "pointer".
#include <iostream>
#include <utility>
template<typename T>
struct bla
{
static void f(const T*) { std::cout << "pointer\n"; }
static void f(const T&) { std::cout << "reference\n"; }
};
int main()
{
bla<std::pair<int,int>>::f({});
}
Changing the std::pair<int,int>
template argument to an int
or any other primitive type, gives the (for me at least) expected "ambiguous overload" error. It seems that builtin types are special here, because any user-defined type (aggregate, non-trivial, with defaulted constructor, etc...) all lead to the pointer overload being called. I believe the template is not necessary to reproduce it, it just makes it simple to try out different types.
Personally, I don't think that is logical and I would expect the ambiguous overload error in all cases, regardless of the template argument. GCC and Clang (and I believe MSVC) all disagree with me, across C++11/14/1z. Note I am fully aware of the bad API these two overloads present, and I would never write something like this, I promise.
So the question becomes: what is going on?
Oh, this is nasty.
Per [over.ics.list]p4 and p7:
4 Otherwise, if the parameter is a non-aggregate class
X
and overload resolution per 13.3.1.7 chooses a single best constructor ofX
to perform the initialization of an object of typeX
from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. [...][...]
6 Otherwise, if the parameter is a reference, see 13.3.3.1.4. [Note: The rules in this section will apply for initializing the underlying temporary for the reference. -- end note] [...]
[...]
7 Otherwise, if the parameter type is not a class:
[...]
(7.2) -- if the initializer list has no elements, the implicit conversion sequence is the identity conversion. [...]
The construction of a const std::pair<int,int>
temporary from {}
is considered a user-defined conversion. The construction of a const std::pair<int,int> *
prvalue, or a const int *
prvalue, or a const int
temporary object are all considered standard conversions.
Standard conversions are preferred over user-defined conversions.
Your own find of CWG issue 1536 is relevant, but mostly for language lawyers. It's a gap in the wording, where the standard doesn't really say what happens for initialisation of a reference parameter from {}
, since {}
is not an expression. It's not what makes the one call ambiguous and the other not though, and implementations are managing to apply common sense here.