After being confused why my code gave me an ambiguity error on GCC but no errors on Clang, I simplified the code. It can be seen below.
struct Foo
{
// Foo(Foo&&) = delete;
// Foo(const Foo&) = delete;
Foo(int*) {}
};
struct Bar
{
template<typename T>
operator T()
{
return Foo{nullptr};
}
};
int main() { Foo f{Bar{}}; }
The errors are as follows.
main.cpp:17:18: error: call to constructor of 'Foo' is ambiguous
int main() { Foo f{Bar{}}; }
^~~~~~~~
main.cpp:1:8: note: candidate is the implicit move constructor
struct Foo
^
main.cpp:1:8: note: candidate is the implicit copy constructor
main.cpp:5:1: note: candidate constructor
Foo(int*) {}
^
I was unable to make it successfullycompile for Clang this time, so I suppose that was just a Clang bug and this is the intended behavior.
When I explicitly delete the copy and move constructors (i.e. uncomment the top two lines of code), I instead get
note: candidate constructor has been explicitly deleted
but still an error. How would I go about disambiguating the construction here, then?
Note that I specifically added the Foo{nullptr}
instead of just nullptr
, but there is no difference. Same with marking the Foo
ctor explicit. This ambiguity error only occurs when Bar
's conversion operator is templated.
I can add some SFINAE to the conversion operator, but I am unsure of what I would exclude. For example, this would make it work:
template<typename T, std::enable_if_t<std::is_same<T, Foo>{}>* = nullptr>
This is another one that I found and this might be my answer:
template<typename T, std::enable_if_t<!std::is_same<T, int*>{}>* = nullptr>
To resolve the ambiguity, add
explicit
to the conversion operator declaration:Why is it necessary? Because
Foo
has a constructor taking aint*
, and so aoperator int*()
instantiation ofoperator T()
template is being considered as part of overload resolution for the initialization off
. See under [over.match.copy]:From (1.2) it follows that only implicit conversion functions are considered for the initialization, hence the ambiguity -- as the compiler cannot decide between constructing
f
using a reference toFoo
or, as already mentioned, using aint*
(obtained by way of copy-initializing) from the return value ofoperator int*
. However, when the initializer expression is a temporary object, we also consider explicit conversions -- but only if they match a constructor taking a reference toFoo
, our "possibly cv-qualifiedT
", i.e. our copy and move constructors. This entire behavior is being consistent with [class.conv.fct¶2]:And so, saying the same thing here for the 3rd time: if it isn't marked as
explicit
, there's nothing stopping the compiler from trying to copy-initialize anint*
to be used for construction.My best guess after some digging: I get the same error with the following code:
And, when I uncomment the commented code, then the problem disappears. It seems to me that, in OP's original templated version, when implicit conversion is required from
Bar
toFoo
, GCC only "instantiates" conversion operator declarations and then resolves overload before instantiating of their bodies.As for why
explicit
helps, it is because in the second case, there is one more conversion required (Bar
→int*
and thenint*
→Foo
).