Bitwize
Bitwize

Reputation: 11220

Compiler discrepancy for out-of-line definitions of conversion operators

There appears to be some discrepancy between compilers as to whether the following code is well formed. In particular, GCC and Clang accept this code -- whereas MSVC rejects it:

template <typename T>
class Bar{};

template <typename T>
struct Foo
{
    using element_type = T;

    operator Bar<element_type>();
};

template <typename T>
Foo<T>::operator Bar<element_type>()
{
    return {};
}

MSVC's rejection message is just a generic:

<source>(11): error C2065: 'element_type': undeclared identifier

The issue only appears to occur when the template argument for the conversion type is a dependent template type, since this is resolved by changing Bar<element_type> to either Bar<T> or Bar<typename Foo<T>::element_type>. I've made a small example on compiler explorer to demonstrate this. This appears to occur from C++11 (possibly older, didn't test) up to C++2a, and is independent of the compiler's versions or flags.

I know that C++ allows dropping the prefixing class-name specifier for types when in the body or parameter list of a class function/constructor definition -- but I'm unsure whether this same short-form is allowed when handling a conversion operator in an out-of-line definition.

Is this an ambiguity in the standard, or a bug in either of the compilers? I'm mostly curious to know how this is defined (or not, as it may be) in the C++ standard.


Edit: To add to the confusion, it appears that MSVC accepts the following code:

template <typename T>
struct Foo
{
    using element_type = T;
    operator element_type();
};

template <typename T>
Foo<T>::operator element_type() // note: no 'typename' syntax
{
    return {};
}

so this doesn't appear to be just an issue caused from conversion to template-dependent type names... I'm suspecting this may be an MSVC bug rather than a discrepancy.

Upvotes: 4

Views: 148

Answers (1)

Davis Herring
Davis Herring

Reputation: 39818

The standard is very vague on the subject. In the case where you're explicitly calling a conversion function (a.operator int()), [basic.lookup.classref]/7 says

If the id-expression is a conversion-function-id, its conversion-type-id is first looked up in the class of the object expression ([class.member.lookup]) and the name, if found, is used.

Unfortunately, a conversion-type-id can have any number of names (including 0), in which case it is impossible to look up directly. [class.qual]/1.2 applies the same non-rule to references to the function using ::, which may or may not apply to out-of-class definitions of such functions (since normal lookup doesn't apply there).

There is massive implementation divergence. In the following example, the comments indicate which compilers (the most current versions at the moment on Compiler Explorer) reject which lines (usually because they can't find the name):

struct X {
  struct A {};
  operator A();

  template<class> struct B {};
  using C = int;
  operator B<C>();

  struct D {using I=int; I i;};
  operator int D::*();
  operator D::I();

  static float E;
  operator decltype(E)();

  struct G {};
  operator struct G();
};

X::operator A() {throw;}            // OK
X::operator B<C>() {throw;}         // MSVC: B and C
X::operator int D::*() {throw;}     // MSVC
X::operator D::I() {throw;}         // MSVC
X::operator decltype(E)() {throw;}  // MSVC
X::operator struct G() {throw;}     // MSVC thinks G is ::G

void f(X x) {
  x.operator A();            // Clang, MSVC
  x.operator B<C>();         // GCC, Clang, ICC: C; MSVC: B and C
  x.operator int D::*();     // Clang
  x.operator D::I();         // Clang
  x.operator decltype(E)();  // Clang, ICC, MSVC; GCC segfaults
  x.operator struct G();     // GCC, Clang, MSVC think G is local to f
}

Clang accepts all the definitions and rejects all the calls because it doesn't implement [basic.lookup.classref]/7 at all but helpfully misapplies [basic.scope.class]/4 within the declarator-id when it is a conversion-function-id.

New rules have been applied (retroactively) to clarify this mess (along with many other messes). The explicit calls involving A, B, D, and G are now valid.

Upvotes: 4

Related Questions