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;
}
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
"c string" == Foo<std::string>("foo")
)).Respuestas:
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 deoperator==
como una plantilla visible.Aquí hay un caso de prueba aún más interesante:
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 laoperator==
línea # 2 no encuentra nada.Pero con
-UWRONG_DECL
GCC en modo C ++ 20 parece decidir que está bien que la búsqueda no calificada paraoperator==
# 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.fuente