Definição fora da classe C ++ 20 em uma classe de modelo

12

Até o padrão C ++ 20 do C ++, quando queríamos definir um operador fora da classe que usa alguns membros privados de uma classe de modelo, usaríamos uma construção semelhante 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;
}

Desde C ++ 20, no entanto, podemos omitir a declaração fora da classe, assim também a declaração direta, para que possamos nos safar apenas:

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

Agora, minha pergunta é: qual parte do C ++ 20 nos permite fazer isso? E por que isso não foi possível nos padrões anteriores do C ++?


Como foi apontado nos comentários, o clang não aceita esse código apresentado na demonstração, o que sugere que isso pode realmente ser um bug no gcc.

Arquivei um relatório de bug no bugzilla de gcc

ProXicT
fonte
2
Pessoalmente, prefiro a definição de classe, evitando a função de modelo (e dedução de "problemas" (sem correspondência para "c string" == Foo<std::string>("foo"))).
Jarod42 18/02
@ Jarod42 Eu concordo totalmente, também prefiro definição em sala de aula. Fiquei surpreso ao descobrir que o C ++ 20 nos permite não repetir a assinatura da função três vezes ao defini-la fora da classe, o que pode ser útil em uma API pública em que a implementação está em um arquivo .inl oculto.
ProXicT 18/02
Eu não notei que era impossível. Como é que eu o usei até agora sem problemas?
ALX23z 18/02
11
Hmmm, em temp.friend , não mudou muito, especialmente não 1.3, que deve ser responsável por esse comportamento. Como o clang não aceita seu código, estou inclinado a ter um bug no gcc.
n314159 18/02
@ ALX23z Funciona sem a declaração fora da classe se a classe não estiver modelada.
ProXicT 18/02

Respostas:

2

O GCC tem um erro.

A pesquisa de nome sempre é realizada para nomes de modelo que aparecem antes de a <, mesmo quando o nome em questão é o nome que está sendo declarado em uma declaração (amigo, especialização explícita ou instanciação explícita).

Como o nome operator==na declaração de amigo é um nome não qualificado e está sujeito à pesquisa de nome em um modelo, as regras de pesquisa de nome em duas fases se aplicam. Nesse contexto, operator==não é um nome dependente (não faz parte de uma chamada de função, portanto a ADL não se aplica); portanto, o nome é pesquisado e encadernado no ponto em que aparece (consulte [temp.nondep] parágrafo 1). Seu exemplo está incorreto porque essa pesquisa de nome não encontra nenhuma declaração de operator==.

Eu esperaria que o GCC estivesse aceitando isso no modo C ++ 20 devido ao P0846R0 , que permite (por exemplo) operator==<T>(a, b)ser usado em um modelo, mesmo que nenhuma declaração anterior operator==como modelo seja visível.

Aqui está um caso de teste ainda mais interessante:

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;

Com -DWRONG_DECL, GCC e Clang concordam que este programa está mal formado: a pesquisa não qualificada da declaração de amigo nº 2, no contexto da definição de modelo, encontra a declaração nº 1, que não corresponde ao amigo instanciado Foo<int>. A declaração nº 3 nem sequer é considerada, porque a pesquisa não qualificada no modelo não a encontra.

Com -UWRONG_DECL, GCC (em C ++ 17 e versões anteriores) e Clang concordam que este programa está mal formado por um motivo diferente: a pesquisa não qualificada operator==da linha 2 não encontra nada.

Porém -UWRONG_DECL, com o GCC no modo C ++ 20 parece decidir que não há problema em que a pesquisa não qualificada operator==no número 2 falhe (provavelmente devido a P0846R0) e, em seguida, parece refazer a pesquisa no contexto de instanciação do modelo, agora encontrando o número 3, em violação da regra de pesquisa de nome de duas fases normal para modelos.

Richard Smith
fonte
Obrigado por esta explicação detalhada, muito bem colocado!
ProXicT 23/02