Nenhum operador == encontrado ao comparar estruturas em C ++

96

Comparando duas instâncias da estrutura a seguir, recebo um erro:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

O erro é:

erro C2678: binário '==': nenhum operador encontrado, o que leva um operando à esquerda do tipo 'myproj :: MyStruct1' (ou não há conversão aceitável)

Por quê?

Jonathan
fonte

Respostas:

126

Em C ++, os structs não têm um operador de comparação gerado por padrão. Você precisa escrever o seu próprio:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
fonte
21
@Jonathan: Por que o C ++ saberia como você quer comparar os seus structs por igualdade? E se você quiser a maneira mais simples, sempre memcmphá tanto tempo que suas estruturas não contêm ponteiro.
Xeo de
12
@Xeo: memcmpfalha com membros não POD (como std::string) e estruturas preenchidas.
fredoverflow,
16
@Jonathan As linguagens "modernas" que conheço fornecem um ==operador --- com uma semântica que quase nunca é a desejada. (E eles não fornecem um meio de substituí-la, então você acaba tendo que usar uma função de membro). As linguagens "modernas" que conheço também não fornecem semântica de valor, então você é forçado a usar ponteiros, mesmo quando eles não são apropriados.
James Kanze,
4
Os casos @Jonathan definitivamente variam, mesmo dentro de um determinado programa. Para objetos de entidade, a solução fornecida por Java funciona muito bem (e, claro, você pode fazer exatamente a mesma coisa em C ++ --- é até mesmo C ++ idiomático para objetos de entidade). A questão é o que fazer com os objetos de valor. C ++ fornece um padrão operator=(mesmo que freqüentemente faça a coisa errada), por razões de compatibilidade com C. A compatibilidade com C não requer um operator==. Globalmente, eu prefiro o que C ++ faz ao que Java faz. (Não sei C #, então talvez seja melhor.)
James Kanze,
9
Pelo menos deve ser possível = default!
user362515
93

C ++ 20 introduziu comparações padrão, também conhecidas como "nave espacial"operator<=> , que permite que você solicite </ <=/ ==/ !=/ >=/ e / ou >operadores gerados pelo compilador com a implementação óbvia / ingênua (?) ...

auto operator<=>(const MyClass&) const = default;

... mas você pode personalizar isso para situações mais complicadas (discutido abaixo). Veja aqui a proposta de idioma, que contém justificativas e discussão. Esta resposta continua relevante para C ++ 17 e anteriores e para uma visão de quando você deve personalizar a implementação de operator<=>....

Pode parecer um pouco inútil para C ++ não ter padronizado isso anteriormente, mas muitas vezes structs / classes têm alguns membros de dados para excluir da comparação (por exemplo, contadores, resultados em cache, capacidade do contêiner, código de sucesso / erro da última operação, cursores), como bem como decisões a tomar sobre uma miríade de coisas, incluindo, mas não se limitando a:

  • quais campos comparar primeiro, por exemplo, comparar um intmembro específico pode eliminar 99% dos objetos desiguais muito rapidamente, enquanto um map<string,string>membro pode muitas vezes ter entradas idênticas e ser relativamente caro para comparar - se os valores são carregados em tempo de execução, o programador pode ter insights sobre o compilador não pode
  • na comparação de strings: distinção entre maiúsculas e minúsculas, equivalência de espaços em branco e separadores, convenções de escape ...
  • precisão ao comparar flutuadores / duplos
  • se os valores de ponto flutuante NaN devem ser considerados iguais
  • comparar ponteiros ou dados apontados (e se for o último, como saber se os ponteiros são para matrizes e de quantos objetos / bytes precisam de comparação)
  • se a ordem é importante ao comparar recipientes não classificados (por exemplo vector, list) e, em caso afirmativo, se não há problema em classificá-los no local antes de comparar vs. usar memória extra para classificar temporários cada vez que uma comparação é feita
  • quantos elementos da matriz atualmente contêm valores válidos que devem ser comparados (há um tamanho em algum lugar ou uma sentinela?)
  • qual membro de uniona comparar
  • normalização: por exemplo, tipos de data podem permitir dia do mês ou mês do ano fora do intervalo, ou um objeto racional / fração pode ter 6/8 enquanto outro tem 3/4, que por motivos de desempenho eles corrigem preguiçosamente com uma etapa de normalização separada; você pode ter que decidir se deve acionar uma normalização antes da comparação
  • o que fazer quando ponteiros fracos não são válidos
  • como lidar com membros e bases que não operator==se implementam (mas podem ter compare()ou operator<ou str()ou getters ...)
  • quais bloqueios devem ser feitos durante a leitura / comparação de dados que outras threads podem querer atualizar

Portanto, é bom ter um erro até que você tenha pensado explicitamente sobre o que a comparação deve significar para sua estrutura específica, em vez de deixá-la compilar, mas não fornecer um resultado significativo em tempo de execução .

Dito isso, seria bom se C ++ lhe permitisse dizer bool operator==() const = default;quando você decidiu que um ==teste membro por membro "ingênuo" estava ok. O mesmo para !=. Dado vários membros / bases, "default" <, <=, >, e >=implementações parecer impossível embora - em cascata com base na ordem de do possível, mas muito improvável que seja o que queria, dada conflitantes imperativos de ordenamento membro (bases de ser, necessariamente, antes de os membros, agrupando por declaração acessibilidade, construção / destruição antes do uso dependente). Para ser mais amplamente útil, C ++ precisaria de um novo membro de dados / sistema de anotação de base para orientar as escolhas - isso seria ótimo ter no Padrão, no entanto, idealmente acoplado com geração de código definido pelo usuário baseado em AST ... Espero isto'

Implementação típica de operadores de igualdade

Uma implementação plausível

É provável que uma implementação razoável e eficiente seja:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Observe que isso também precisa de um operator==para MyStruct2.

As implicações desta implementação, e alternativas, são discutidas sob o título Discussão das especificações de seu MyStruct1 abaixo.

Uma abordagem consistente para ==, <,> <= etc

É fácil alavancar std::tupleos operadores de comparação para comparar suas próprias instâncias de classe - use apenas std::tiepara criar tuplas de referências a campos na ordem de comparação desejada. Generalizando meu exemplo a partir daqui :

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

Quando você "possui" (ou seja, pode editar, um fator com bibliotecas corporativas e de terceiros) a classe que deseja comparar, e especialmente com a preparação do C ++ 14 para deduzir o tipo de retorno de função da returninstrução, geralmente é melhor adicionar um " vincule a "função de membro à classe que você deseja comparar:

auto tie() const { return std::tie(my_struct1, an_int); }

Então, as comparações acima simplificam para:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Se você quiser um conjunto mais completo de operadores de comparação, sugiro operadores boost (pesquisar por less_than_comparable). Se não for adequado por algum motivo, você pode ou não gostar da ideia de macros de suporte (online) :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... que pode então ser usado a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(Versão C ++ 14 vinculado a membros aqui )

Discussão dos detalhes de sua MyStruct1

Existem implicações na escolha de fornecer um membro independente versus um membro operator==()...

Implementação autônoma

Você tem uma decisão interessante a tomar. Como sua classe pode ser construída implicitamente a partir de um MyStruct2, uma função independente / não membro bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)suportaria ...

my_MyStruct2 == my_MyStruct1

... criando primeiro um de temporário MyStruct1e my_myStruct2, em seguida, fazendo a comparação. Isso definitivamente deixaria MyStruct1::an_intdefinido para o valor do parâmetro padrão do construtor de -1. Dependendo se você incluir an_intcomparação na implementação do seu operator==, um MyStruct1pode ou não comparar igual a uma MyStruct2que se compara igual ao MyStruct1do my_struct_2membro! Além disso, criar um temporário MyStruct1pode ser uma operação muito ineficiente, pois envolve a cópia do my_struct2membro existente para um temporário, apenas para descartá-lo após a comparação. (Claro, você poderia evitar esta construção implícita de MyStruct1s para comparação, tornando esse construtor explicitou removendo o valor padrão para an_int.)

Implementação de membro

Se você quiser evitar a construção implícita de a MyStruct1de a MyStruct2, torne o operador de comparação uma função-membro:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Observe a constpalavra-chave - necessária apenas para a implementação do membro - avisa o compilador que a comparação de objetos não os modifica, portanto, pode ser permitida em constobjetos.

Comparando as representações visíveis

Às vezes, a maneira mais fácil de obter o tipo de comparação que deseja pode ser ...

    return lhs.to_string() == rhs.to_string();

... o que geralmente é muito caro também - aqueles stringsão criados dolorosamente para serem jogados fora! Para tipos com valores de ponto flutuante, comparar representações visíveis significa que o número de dígitos exibidos determina a tolerância dentro da qual valores quase iguais são tratados como iguais durante a comparação.

Tony Delroy
fonte
Bem, na verdade, para os operadores de comparação <,>, <=,> = só deve ser necessário implementar <. O resto segue, e não há maneira significativa de implementá-los, significando algo diferente do que a implementação que pode ser gerada automaticamente. É bizarro você ter que implementá-los todos sozinho.
André
@ André: mais frequentemente uma escrita manualmente int cmp(x, y)ou comparefunção que retorna um valor negativo para x < y, 0 para a igualdade e um valor positivo para x > yé usado como base para <, >, <=, >=, ==, e !=; é muito fácil usar o CRTP para injetar todos esses operadores em uma classe. Tenho certeza de que postei a implementação em uma resposta antiga, mas não consegui encontrar rapidamente.
Tony Delroy
@TonyD Claro que você pode fazer isso, mas é tão fácil de implementar >, <=e >=em termos de <. Você também poderia implementar ==e !=dessa forma, mas isso geralmente não seria uma implementação muito eficiente, eu acho. Seria bom se nenhum CRTP ou outros truques fossem necessários para tudo isso, mas o padrão apenas obrigaria a autogeração desses operadores se não fosse explicitamente definido pelo usuário e <fosse definido.
André
@ André: é porque ==e !=não pode ser eficientemente expresso usando <que o uso de comparar para tudo é comum. "Seria bom se não CRTP ou outros truques seriam necessários" - talvez, mas, em seguida, CRTP pode ser facilmente usado para gerar lotes de outros operadores (por exemplo, bit a bit |, &, ^de |=, &=e ^=, + - * / %a partir de suas formas de atribuição; binário -de negação unário e +) - tantas variações potencialmente úteis sobre este tema que apenas fornecer um recurso de linguagem para uma parte bastante arbitrária disso não é particularmente elegante.
Tony Delroy
Você se importaria de adicionar a uma implementação plausível uma versão que usa std::tiepara fazer a comparação de vários membros?
NathanOliver de
17

Você precisa definir explicitamente operator ==para MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Agora a comparação == é válida para 2 desses objetos.

iammilind
fonte
11

Iniciando em C ++ 20, deve ser possível adicionar um conjunto completo de operadores de comparação padrão ( ==, <=, etc.) para uma classe declarando uma de três vias operador de comparação padrão ( "nave espacial" operador), como este:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Com um compilador C ++ 20 compatível, adicionar essa linha a MyStruct1 e MyStruct2 pode ser suficiente para permitir comparações de igualdade, presumindo que a definição de MyStruct2 seja compatível.

Joe Lee
fonte
2

A comparação não funciona em estruturas em C ou C ++. Em vez disso, compare por campos.

Rafe Kettler
fonte
2

Por padrão, as estruturas não têm um ==operador. Você terá que escrever sua própria implementação:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
fonte
0

Por padrão, o operador == funciona apenas para primitivos. Para fazer seu código funcionar, você precisa sobrecarregar o operador == para sua estrutura.

Babak Naffas
fonte
0

Porque você não escreveu um operador de comparação para sua estrutura. O compilador não o gera para você, portanto, se você deseja comparação, deve escrevê-lo você mesmo.


fonte