Tentei usar ambos como padrão operator==
em operator<=>
uma classe simples que contém um membro de referência como este:
#include <iostream>
#include <string>
class Simple
{
public:
Simple(const std::string& data)
: data_(data)
{
}
auto operator<=>(const Simple&) const = default;
private:
const std::string& data_;
};
int main(int argc, char** argv)
{
std::string str1 = "one";
Simple s1(str1);
std::string str2 = "two";
Simple s2(str2);
std::cout << (s1 < s2) << std::endl; // compiler error
return 0;
}
O compilador clang afirma que
warning: explicitly defaulted three-way comparison operator is implicitly deleted
note: defaulted 'operator<=>' is implicitly deleted because class 'Simple' has a reference member
Não recebi nenhum aviso de outros compiladores (por exemplo, MSVC), mas quando tento usá-lo, recebo erros de compilação:
<source>(62): error C2280: 'auto Simple::operator <=>(const Simple &) const': attempting to reference a deleted function
<source>(49): note: see declaration of 'Simple::operator <=>'
<source>(49): note: 'auto Simple::operator <=>(const Simple &) const': function was implicitly deleted because 'Simple' data member 'Simple::data_' of type 'const std::string &' is a reference type
<source>(52): note: see declaration of 'Simple::data_'
Outras funções padrão, como atribuição de cópia, serão excluídas, porque não são possíveis com um membro de referência.
Mas por que o conteúdo de uma referência não pode ser comparado automaticamente?
E qual é o caminho mais curto para implementá-lo manualmente?
O artigo P1603R1 especifica que os membros de referência devem levar a operadores de comparação excluídos, dizendo que isso está de acordo com o artigo P0515:
O artigo continua dizendo (desta vez a ênfase é minha):
Ou seja, a escolha mais segura foi feita e, se houver motivação suficiente para uma mudança de regras, uma mudança poderá ser feita.
Os membros de referência de fato impedem que o compilador gere os operadores de comparação padrão , porque você pode comparar o conteúdo ao qual a referência se refere ou os endereços.
Você pode implementar
operator<=>
manualmente da seguinte maneira:Demonstração do Godbolt .
Você pode implementar de forma semelhante
operator==
para verificar a igualdade.O problema aqui é que
operator<=>
eoperator==
não pode ser padronizado em classes com membros de referência porque as implementações padrão dependem da comparação direta de todos os membros de dados. No entanto, referências não suportam atribuição de cópia ou operações de movimentação, então o compilador não pode sintetizar esses operadores quando referências estão envolvidas.Quando o compilador tenta usar o
operator<=>
, ele tenta comparar cada membro individualmente. Mas comodata_
é uma referência, ele não tem um valor próprio — ele apenas aponta para outro objeto, então o compilador não pode executar comparações diretas como faria para um tipo de valor. É por isso que o operador de comparação é implicitamente excluído.Você precisará implementar
operator==
eoperator<=>
comparar manualmente o objeto ao qual os pontos de referência apontam, em vez da referência em si. Veja como você pode fazer isso:operator==
: Definimos manualmenteoperator==
para comparardata_
ems1
es2
por meio da comparação dos objetos aos quais eles fazem referência.operator<=>
: Similarmente, definimos manualmenteoperator<=>
para comparardata_
ems1
es2
usandooperator<=>
onstd::string
. Comodata_
é uma referência,data_ <=> other.data_
realiza uma comparação entre osstd::string
objetos aos quais as referências apontam.Como referências não são valores em si, elas precisam ser comparadas pelos objetos aos quais fazem referência. Esta implementação manual contorna a incapacidade do compilador de usar default
operator==
eoperator<=>
para tipos com membros de referência. Esta é a maneira mais curta e limpa de lidar com a situação sem alterar o design da classe.