Por que a desigualdade é testada como (! (A == b)) em muitos códigos de biblioteca padrão do C ++?

142

Este é o código do código da biblioteca padrão C ++ remove. Por que a desigualdade é testada em if (!(*first == val))vez de if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }
Ahmed Nawar
fonte
2
@BeyelerStudios provavelmente está correto. Também é comum na implementação operator!=. Basta usar a operator==implementação:bool operator!=(const Foo& other) { return !(*this == other); }
simon
1
na verdade eu corrigir minha afirmação: as referências mencionar remove todos os elementos que são iguais a valor tão operator==é uma espécie de espera para ser usado aqui ...
BeyelerStudios
Ah, e também deve haver um constexemplo no meu comentário anterior, mas você entendeu. (Tarde demais para editá-lo)
simon
A razão para isso está relacionada a outra pergunta (que pode basicamente responder com "Não, não necessariamente") e ao conceito de ser EqualityComparableque Hurkyl mencionou em sua resposta .
precisa saber é o seguinte

Respostas:

144

Porque isso significa que o único requisito em T é implementar um operator==. Você pode exigir que T tenha uma, operator!=mas a idéia geral aqui é que você deve colocar o mínimo de carga possível sobre o usuário do modelo e outros modelos precisam operator==.

Tom Tanner
fonte
13
operador bool inline do modelo <classe T>! = <TA, TB> {return! (a == b); }
Joshua
8
Haveria algum cenário em que um compilador não pudesse trocar todas as instâncias de =! para! (==)? Por que isso ainda não seria a ação padrão para o compilador executar?
Aidan Gomez
20
@AidanGomez Para melhor ou pior, você pode sobrecarregar os operadores para fazer o que quiser. Não precisa fazer sentido ou ser consistente.
Neil Kirk
10
x != ynão está definido para ser o mesmo que !(x == y). E se esses operadores retornarem a árvore de análise de uma DSL incorporada?
Brice M. Dempsey
7
@ Josué Isso quebra muito se tentar usar o SFINAE para detectar se !=é suportado (retornaria incorretamente verdadeiro - mesmo que operator==não seja suportado!). Também estou preocupado que isso faça com que alguns usos !=se tornem ambíguos.
36

A maioria das funções no STL funciona apenas com operator<ou operator==. Isso requer que o usuário implemente apenas esses dois operadores (ou às vezes pelo menos um deles). Por exemplo, std::setusa operator<(mais precisamente o std::lessque chama operator<por padrão) e não operator>para gerenciar pedidos. O removemodelo no seu exemplo é um caso semelhante - ele usa apenas operator==e não, operator!=portanto operator!=, não precisa ser definido.

Lukáš Bednařík
fonte
2
As funções não são usadas operator<diretamente, mas sim std::less, que, por sua vez, é padronizado como operator<.
Christian Hackl
1
Na verdade, parece que as funções padrão do algoritmo, ao contrário de std::set, por exemplo , realmente usam operator<diretamente. Estranho ...
Christian Hackl
1
Essas funções não usam std::equal_to, elas usam operator==conforme observado na pergunta. A situação com std::lessé semelhante. Bem, talvez std::setnão seja o melhor exemplo.
Lukáš Bednařík
2
@ChristianHackl std::equal_toe std::lesssão usados ​​como parâmetros padrão do modelo em que o comparador é usado como parâmetro. operator==e operator<são usados ​​diretamente quando o tipo é necessário para satisfazer a igualdade de ordens comparáveis ​​e estritas, respectivamente, por exemplo, iteradores e iteradores de acesso aleatório.
Jan Hudec
28

Este é o código do código de remoção da biblioteca padrão C ++.

Errado. Não é oremove código de biblioteca padrão do C ++ . É uma possível implementação interna da removefunção de biblioteca padrão C ++ . O padrão C ++ não prescreve código real; prescreve protótipos de função e comportamentos necessários.

Em outras palavras: Do ponto de vista estrito do idioma, o código que você está vendo não existe . Pode ser de algum arquivo de cabeçalho que acompanha a implementação da biblioteca padrão do seu compilador. Observe que o padrão C ++ nem exige que esses arquivos de cabeçalho existam. Os arquivos são apenas uma maneira conveniente para os implementadores de compiladores atenderem aos requisitos de uma linha como #include <algorithm>(por exemplo, disponibilização std::removee outras funções).

Por que a desigualdade é testada em if (!(*first == val))vez de if (*first != val)?

Porque somente operator==é requerido pela função.

Quando se trata de sobrecarga de operador para tipos personalizados, o idioma permite que você faça todos os tipos de coisas estranhas. Você pode muito bem criar uma classe que tenha uma sobrecarga, operator==mas não sobrecarregada operator!=. Ou pior: você pode sobrecarregar, operator!=mas fazer com que coisas completamente não relacionadas.

Considere este exemplo:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Se std::removeusado operator!=, o resultado seria bem diferente.

Christian Hackl
fonte
1
Outra coisa a considerar é que pode ser possível para ambos a==be a!=bretornar falso. Embora nem sempre seja claro se tal situação seria considerada de maneira mais significativa como "igual" ou "não igual", uma função que define igualdade apenas em termos do operador "==" deve considerá-los como "não iguais" ", independentemente de qual comportamento faria mais sentido [se eu tivesse meus controladores, todos os tipos devem fazer com que os operadores" == "e"! = "com rendimento booleano se comportem de maneira consistente, mas o fato de o IEEE-754 exigir igualdade de direitos operadores dificultariam tal expectativa].
Supercat
18
"Do ponto de vista estrito da linguagem, o código que você está vendo não existe." - quando um ponto de vista diz que algo não existe, mas você está realmente olhando para aquilo, então o ponto de vista está errado. Na verdade, o padrão não diz o código não existe, ele simplesmente não diz ela existe :-)
Steve Jessop
@SteveJessop: Você pode substituir a expressão "do ponto de vista estrito do idioma" por algo como "estritamente no nível do idioma". O ponto é que o código publicado pelo OP não é da preocupação do padrão de idioma.
Christian Hackl
2
@ supercat: O IEEE-754 faz ==e !=se comporta de maneira consistente, embora eu sempre tenha pensado que todas as seis relações deveriam avaliar falsequando pelo menos um operando é NaN.
Ben Voigt
@BenVoigt: Ah, está certo. Isso faz com que os dois operadores se comportem da mesma maneira quebrada, de modo que sejam consistentes entre si, mas ainda assim violem todos os outros axiomas normais associados à equivalência (por exemplo, eles não sustentam a == a, nem a garantia de que as operações executadas em valores iguais produzirá resultados iguais).
supercat
15

Algumas boas respostas aqui. Eu só queria adicionar uma pequena nota.

Como todas as boas bibliotecas, a biblioteca padrão é projetada com (pelo menos) dois princípios muito importantes em mente:

  1. Coloque a menor quantidade de responsabilidade nos usuários da sua biblioteca com a qual você pode se safar. Parte disso tem a ver com oferecer o menor trabalho possível ao usar sua interface. (como definir o menor número possível de operadores). A outra parte tem a ver com não surpreendê-los ou exigir que eles verifiquem códigos de erro (portanto, mantenha as interfaces consistentes e crie exceções <stdexcept>quando as coisas dão errado).

  2. Elimine toda redundância lógica . Todas as comparações podem ser deduzidas apenas a partir de operator<, então por que exigir que os usuários definam outras? por exemplo:

    (a> b) é equivalente a (b <a)

    (a> = b) é equivalente a! (a <b)

    (a == b) é equivalente a! ((a <b) || (b <a))

    e assim por diante.

    Obviamente, nesta nota, pode-se perguntar por que unordered_maprequer operator==(pelo menos por padrão) e não operator<. A resposta é que, em uma tabela de hash, a única comparação que exigimos é a igualdade. Portanto, é mais logicamente consistente (isto é, faz mais sentido para o usuário da biblioteca) exigir que ele defina um operador de igualdade. Exigir um operator<seria confuso, porque não é imediatamente óbvio por que você precisaria.

Richard Hodges
fonte
10
Em relação ao seu segundo ponto: Existem tipos que podem ser comparados logicamente para igualdade, mesmo que não exista uma ordem lógica ou para os quais o estabelecimento de uma ordem seria muito artificial. Por exemplo, vermelho é vermelho e vermelho não é verde, mas é inerentemente menor que verde?
Christian Hackl
1
Totalmente de acordo. Não seria possível armazenar esses itens em um contêiner solicitado porque não há ordem lógica. Eles podem ser armazenados de maneira mais adequada em um recipiente não ordenado que requer operator==(e hash).
Richard Hodges
3. Sobrecarregue o mínimo de operadores padrão possível. Essa é outra razão pela qual eles implementaram !(a==b). Como a sobrecarga não refletida de operadores pode facilmente fazer com que o programa C ++ fique completamente confuso (além disso, enlouquece o programador porque a depuração de seu código pode se tornar uma missão impossível, pois encontrar o culpado de um bug específico é semelhante a uma odisseia).
syntaxerror 29/09/15
!((a < b) || (b < a))usa menos um operador booleano, por isso é provavelmente mais rápido
Filip Haglund
1
Na realidade eles não. Na linguagem assembly, todas as comparações são implementadas como um subtrato seguido pelo teste carry e zero bits no registrador de sinalizadores. Tudo o resto é apenas açúcar sintático.
Richard Hodges
8

O EqualityComparableconceito requer apenas que operator==seja definido.

Conseqüentemente, qualquer função que professa trabalhar com tipos satisfatórios EqualityComparable não pode contar com a existência de operator!=objetos desse tipo. (a menos que haja requisitos adicionais que impliquem a existência de operator!=).


fonte
1

A abordagem mais promissora é encontrar um método para determinar se o operador == pode ser chamado para um tipo específico e, em seguida, apoiá-lo somente quando estiver disponível; em outras situações, uma exceção seria lançada. No entanto, até o momento não há maneira conhecida de detectar se uma expressão arbitrária do operador f == g está definida adequadamente. A melhor solução conhecida possui as seguintes qualidades indesejáveis:

  • Falha no tempo de compilação para objetos em que o operador == não está acessível (por exemplo, porque é privado).
  • Falha no tempo de compilação se o operador de chamada == for ambíguo.
  • Parece estar correto se a declaração do operador == estiver correta, mesmo que o operador == possa não ser compilado.

Do Boost FAQ: fonte

Sabendo que exigir uma ==implementação é um fardo , você nunca deseja criar um ônus adicional ao exigir também a !=implementação.

Para mim, pessoalmente, é sobre o SOLID (projeto orientado a objetos) L parte - princípio de substituição de Liskov: "os objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção desse programa". Nesse caso, é o operador ! = Que eu posso substituir por == e booleano inverso na lógica booleana.

Margus
fonte