What are the properties of template conversion `operator const T &` in C++?

Viewed 215

Every C++ class can declare conversion operators in other types and in particular the operator can be template and can convert to const reference type. But it looks like this operator is treated differently by the modern compilers. The next example:

struct B {};
struct A {
    template <typename T> operator const T &();
    operator B() = delete;
};

int main() { 
    A a;
    [[maybe_unused]] B b(a); //error in Clang
    [[maybe_unused]] int c(a); //error in GCC
}

is fully accepted by MSVC.

GCC accepts a conversion from A to B despite the fact that non-template A::operator B, which I would expect to be preferred, is explicitly deleted.

At the same time GCC rejects the conversion from A to int :

cannot convert 'A' to 'int' in initialization

Demo: https://gcc.godbolt.org/z/WvafjPPjr

I personally prefer the behavior of Clang here, but is it actually right?

1 Answers

B b(a); is direct-initialization for class type. Per [dcl.init.general], constructors of B are considered in overload resolution.

B has three implicitly-generated constructors:

B();         // #1
B(const B&); // #2
B(B&&);      // #3

Per [over.match.viable], only #2 and #3 are viable, as #1 does not have enough parameters.

Overload resolution tries to form an implicit conversion sequence from a to the parameter type for each constructor.

Per [conv.general], conversion to reference is equivalent to reference initialization.

Per [dcl.init.ref], in the initialization of const B&, if the source expression can be converted to an lvalue of type const B via operator const B&, then the reference binds to the result of conversion.

Again per [dcl.init.ref], in the initialization of B&&, if the source expression can be converted to an rvalue of type B via operator B, then the reference binds to the result of conversion. Otherwise, again per [dcl.init.ref], other user-defined conversions are considered.

Since a cannot be converted to B&& in any way, and it can be converted to const B&, #2 wins. So a is converted to const B& and then is used to initialize b via the copy constructor.

int c(a); is direct-initialization for non-class type. Per [dcl.init.general], conversion functions of A are considered in overload resolution.

Per [temp.deduct.conv], the template argument of operator const T & can be deduced as int.

Since the result of operator const int& can be converted to int, the initialization is well-formed.

So I believe that both initializations are valid. That is, MSVC is right. However, the "can be converted" phrasing in [dcl.init.ref] is sloppy and might intend to mean that a value can theoretically be converted via a deleted conversion function, in which case Clang might be right.

Related