How to disambiguate this construction in a templat

2020-06-10 03:41发布

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> 

2条回答
劫难
2楼-- · 2020-06-10 04:39

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.

查看更多
老娘就宠你
3楼-- · 2020-06-10 04:46

My best guess after some digging: I get the same error with the following code:

struct Foo { Foo(int*) {} };

struct Bar {    
   operator Foo(); // { return Foo{nullptr}; }
   /* explicit */ operator int*();
};

int main() { Foo f{Bar{}}; }

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 to Foo, 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 (Barint* and then int*Foo).

查看更多
登录 后发表回答