Comportamento C ++ 20 quebrando código existente com operador de igualdade?
Corri para isso ao depurar esta questão .
Eu reduzi tudo para usar apenas operadores de reforço :
Explorador de compiladores C ++ 17 C ++ 20
#include <boost/operators.hpp> struct F : boost::totally_ordered1<F, boost::totally_ordered2<F, int>> { /*implicit*/ F(int t_) : t(t_) {} bool operator==(F const& o) const { return t == o.t; } bool operator< (F const& o) const { return t < o.t; } private: int t; }; int main() { #pragma GCC diagnostic ignored "-Wunused" F { 42 } == F{ 42 }; // OKAY 42 == F{42}; // C++17 OK, C++20 infinite recursion F { 42 } == 42; // C++17 OK, C++20 infinite recursion }
Este programa compila e funciona bem com C ++ 17 (habilitado para ubsan / asan) tanto no GCC quanto no Clang.
Quando você altera o construtor implícito para
explicit
, as linhas problemáticas obviamente não compilam mais em C ++ 17
Surpreendentemente, ambas as versões compilam em C ++ 20 ( v1 e v2 ) , mas levam à recursão infinita (falha ou loop fechado, dependendo do nível de otimização) nas duas linhas que não compilariam em C ++ 17.
Obviamente, esse tipo de bug silencioso surgindo com a atualização para o C ++ 20 é preocupante.
Questões:
- Este comportamento do c ++ 20 está em conformidade (espero que sim)
- O que exatamente está interferindo? Eu suspeito que pode ser devido ao novo suporte a "operador de nave espacial" do c ++ 20, mas não entendo como ele muda o comportamento deste código.
Respostas
Na verdade, C ++ 20 infelizmente torna esse código infinitamente recursivo.
Aqui está um exemplo reduzido:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
// member: #1
bool operator==(F const& o) const { return t == o.t; }
// non-member: #2
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
Vamos apenas dar uma olhada 42 == F{42}
.
Em C ++ 17, tínhamos apenas um candidato: o candidato não membro ( #2
), então o selecionamos. Seu próprio corpo x == y
tem apenas um candidato: o candidato a membro ( #1
), que envolve a conversão implícita y
em um F
. E então esse candidato a membro compara os dois membros inteiros e está tudo bem.
Em C ++ 20, a expressão inicial 42 == F{42}
agora tem dois candidatos: o candidato a não membro ( #2
) como antes e agora também o candidato a membro #1
invertido ( invertido). #2
é a melhor correspondência - nós combinamos exatamente os dois argumentos em vez de invocar uma conversão, então ela é selecionada.
Agora, porém, x == y
agora tem dois candidatos: o candidato membro novamente ( #1
), mas também o candidato não membro #2
invertido ( invertido). #2
é a melhor correspondência novamente pelo mesmo motivo que era uma correspondência melhor antes: nenhuma conversão necessária. Portanto, avaliamos em y == x
vez disso. Recursão infinita.
Candidatos não revertidos são preferidos aos candidatos revertidos, mas apenas como critério de desempate. A melhor sequência de conversão está sempre em primeiro lugar.
Ok, ótimo, como podemos consertar? A opção mais simples é remover totalmente o candidato não membro:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
private:
int t;
};
42 == F{42}
aqui avalia como F{42}.operator==(42)
, o que funciona bem.
Se quisermos manter o candidato não membro, podemos adicionar seu candidato invertido explicitamente:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
Isso faz com que 42 == F{42}
ainda escolha o candidato não membro, mas agora x == y
no órgão haverá preferência pelo candidato membro, que então faz a igualdade normal.
Esta última versão também pode remover o candidato não membro. O seguinte também funciona sem recursão para todos os casos de teste (e é como eu escreveria comparações em C ++ 20 daqui para frente):
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
private:
int t;
};