Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disambiguate this construction in a templated conversion operator?

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> 
like image 228
Post Self Avatar asked Aug 22 '18 18:08

Post Self


1 Answers

To resolve the ambiguity, add explicit to the conversion operator declaration:

struct Bar
{    
    template<typename T>
    explicit operator T()
    {
        return Foo{nullptr}; 
    }
};

Why is it necessary? Because Foo has a constructor taking a int*, and so a operator int*() instantiation of operator T() template is being considered as part of overload resolution for the initialization of f. See under [over.match.copy]:

1 [...] Assuming that cv1 T is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • (1.1) The converting constructors of T are candidate functions.

  • (1.2) When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to possibly cv-qualified T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion functions are also considered.

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 to Foo or, as already mentioned, using a int* (obtained by way of copy-initializing) from the return value of operator 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 to Foo, our "possibly cv-qualified T", i.e. our copy and move constructors. This entire behavior is being consistent with [class.conv.fct¶2]:

A conversion function may be explicit ([dcl.fct.spec]), in which case it is only considered as a user-defined conversion for direct-initialization ([dcl.init]). Otherwise, user-defined conversions are not restricted to use in assignments and initializations.

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 an int* to be used for construction.

like image 128
Geezer Avatar answered Nov 15 '22 16:11

Geezer