Definición de C ++ 20 fuera de clase en una clase de plantilla

12

Hasta el estándar C ++ 20 de C ++, cuando queríamos definir un operador fuera de clase que usara algunos miembros privados de una clase de plantilla, usaríamos una construcción similar a esta:

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

Sin embargo, desde C ++ 20, podemos omitir la declaración fuera de clase, por lo tanto, también la declaración de reenvío, por lo que podemos salir con solo:

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

Ahora, mi pregunta es, ¿qué parte de C ++ 20 nos permite hacerlo? ¿Y por qué no era esto posible en estándares anteriores de C ++?


Como se señaló en los comentarios, clang no acepta este código presentado en la demostración, lo que sugiere que esto podría ser un error en gcc.

Archivé un informe de error en bugzilla de gcc

ProXicT
fuente
2
Personalmente prefiero en la definición de clase, evitando la función de plantilla (y los "problemas" de deducción (No coincide para "c string" == Foo<std::string>("foo"))).
Jarod42
@ Jarod42 Estoy totalmente de acuerdo, prefiero la definición en clase también. Me sorprendió descubrir que C ++ 20 nos permite no repetir la firma de la función tres veces al definirla fuera de clase, lo que puede ser útil en una API pública donde la implementación está en un archivo .inl oculto.
ProXicT
No me he dado cuenta de que era imposible. ¿Cómo es que lo he usado hasta ahora sin problemas?
ALX23z
1
Hmmm, en temp.friend , no ha cambiado mucho, especialmente no 1.3, que debería ser responsable de este comportamiento. Como clang no acepta tu código, me inclino por que gcc tenga un error.
n314159
@ ALX23z Funciona sin la declaración de fuera de clase si la clase no tiene plantilla.
ProXicT

Respuestas:

2

GCC tiene un error.

La búsqueda de nombres siempre se realiza para los nombres de plantilla que aparecen antes de un < , incluso cuando el nombre en cuestión es el nombre que se declara en una declaración (amigo, especialización explícita o instanciación explícita).

Debido a que el nombre operator==en la declaración de amigo es un nombre no calificado y está sujeto a la búsqueda de nombres en una plantilla, se aplican las reglas de búsqueda de nombres en dos fases. En este contexto, operator==no es un nombre dependiente (no es parte de una llamada de función, por lo que ADL no se aplica), por lo que el nombre se busca y se vincula en el punto donde aparece (ver [temp.nondep] párrafo 1). Su ejemplo está mal formado porque esta búsqueda de nombre no encuentra declaración deoperator== .

Esperaría que GCC esté aceptando esto en modo C ++ 20 debido a P0846R0 , que permite (por ejemplo)operator==<T>(a, b) usarse en una plantilla incluso si no hay una declaración previa de operator==como una plantilla visible.

Aquí hay un caso de prueba aún más interesante:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

Con -DWRONG_DECL, GCC y Clang acuerdan que este programa está mal formado: la búsqueda no calificada de la declaración de amigo # 2, en el contexto de la definición de plantilla, encuentra la declaración # 1, que no coincide con el amigo instanciado deFoo<int> . La declaración n. ° 3 ni siquiera se considera, porque la búsqueda no calificada en la plantilla no la encuentra.

Con -UWRONG_DECL , GCC (en C ++ 17 y anteriores) y Clang están de acuerdo en que este programa está mal formado por una razón diferente: la búsqueda no calificada de la operator==línea # 2 no encuentra nada.

Pero con -UWRONG_DECLGCC en modo C ++ 20 parece decidir que está bien que la búsqueda no calificada para operator==# 2 falle (presumiblemente debido a P0846R0), y luego parece rehacer la búsqueda desde el contexto de creación de instancias de plantilla, ahora encontrando # 3, en violación de la regla de búsqueda de nombre de dos fases normal para plantillas.

Richard Smith
fuente
Gracias por esta explicación detallada, muy bien puesto!
ProXicT