Por que usar “b <a? a: b "em vez de" a <b? b: a ”para implementar o modelo máximo?

154

Modelos C ++ - O Guia Completo, 2ª Edição, apresenta o modelo máximo :

template<typename T>
T max (T a, T b)
{
  // if b < a then yield a else yield b
  return  b < a ? a : b;
}

E explica como usar, em “b < a ? a : b”vez de “a < b ? b : a”:

Observe que o modelo max () de acordo com [StepanovNotes] retorna intencionalmente “b <a? a: b "em vez de" a <b? b: a ”para garantir que a função se comporte corretamente, mesmo que os dois valores sejam equivalentes, mas não iguais.

Como entender " even if the two values are equivalent but not equal."? “a < b ? b : a”parece ter o mesmo resultado para mim.

Nan Xiao
fonte
8
Parece errado para mim ... Ambas as respostas são "correta", mas se ae bsão equivalentes , em seguida, !(a < b) && !(b < a)é verdade, então a < be b < asão ambas falsas, portanto, em b < a ? a : b, bé devolvido, o que não é o que você quer ... Você quer a < b ? b : a.
Holt
1
Muitas vezes você pode distinguir entre equivalente ae bcom std::addressofet. al.
Caleth
14
Se o fizer a = max(a, b);(repetidamente), talvez você não queira substituí-lo adesnecessariamente.
Bo Persson
2
BTW, esse modelo deve usar parâmetros por const-reference e retorná-los por const-reference, caso contrário, você estará fazendo várias cópias inúteis (e substituirá apor uma cópia de a).
Holt
3
@Caleth: O tipo canônico que possui equivalência e igualdade é o CaseInsensitiveString. Para esse tipo, nem a <A nem A <a. Mas std::addressofé irrelevante. De fato, pelo T max(T a, T b)que já sabemos addressof(a) != addressof(b).
MSalters

Respostas:

150

std::max(a, b)é de fato especificado para retornar aquando os dois forem equivalentes.

Isso é considerado um erro por Stepanov e outros porque quebra a propriedade útil que é dada ae b, você sempre pode classificá-los {min(a, b), max(a, b)}; para isso, você gostaria max(a, b)de retornar bquando os argumentos forem equivalentes.

TC
fonte
48
A partir desse link "É difícil para mim culpar as pessoas que o fazem: afinal, elas apenas seguem a especificação padrão C ++ do max escrita por mim. Levei vários anos para perceber que eu estava enganado". - Uau!
Jack Aidley
23
você não pode simplesmente fazer {min(a, b), max(b, a)}?
Capitão Homem
12
@CaptainMan: Sim, mas ainda é menos óbvio. Eu argumentaria que faz sentido lógico que max(a,b)retorne um retorno se-e-se- min(a,b)b, e vice-versa, de modo que eles sejam o inverso um do outro e o conjunto (não ordenado) {min(a,b), max(a,b)}seja sempre igual a {a,b}.
Jack Aidley
5
@ jpmc26: Se alguém está, por exemplo, classificando uma lista de eventos por tempo, não precisa se preocupar se a operação de classificação é estável para se preocupar em ter cada evento que apareça exatamente uma vez na entrada, da mesma forma exatamente exatamente uma vez na saída. Certas operações (como encontrar e eliminar eventos duplicados) podem exigir o uso de uma ordem completa, mas em muitos outros casos, pode ser aceitável listar eventos simultâneos em ordem arbitrária, mas não duplicá-los ou omiti-los.
Supercat
2
@supercat A aplicação mine maxa qualquer coisa, exceto o carimbo de data / hora (a chave de classificação) nesse cenário, não faz sentido. Os eventos (os objetos) em si nem deveriam ser comparáveis ​​se a igualdade não implicar intercambialidade. A única maneira {min(a, b), max(a, b)}faz qualquer sentido como um mecanismo de classificação é se os objetos são intercambiáveis.
jpmc26
62

Esta resposta explica por que o código fornecido está errado do ponto de vista padrão do C ++, mas está fora de contexto.

Veja a resposta do @ TC para uma explicação contextual.


O padrão define da std::max(a, b)seguinte forma [alg.min.max] (a ênfase é minha):

template<class T> constexpr const T& max(const T& a, const T& b);

Requer : O tipo T é LessThanComparable (Tabela 18).

Retorna : O valor maior.

Comentários : Retorna o primeiro argumento quando os argumentos são equivalentes.

Equivalente aqui significa que !(a < b) && !(b < a)é true [alg.sorting # 7] .

Em particular, se ae bsão equivalentes, ambos a < be b < asão false, então o valor à direita de :será retornado no operador condicional, portanto a, deve estar à direita, portanto:

a < b ? b : a

... parece ser a resposta correta. Esta é a versão usada pelo libstdc ++ e libc ++ .

Portanto, as informações em sua cotação parecem erradas de acordo com o padrão atual, mas o contexto em que são definidas pode ser diferente.

Holt
fonte
4
Link Godbolt que explica o problema (obrigado @songyuanyao pela definição de X).
Holt
1
@JackAidley Editei a resposta para especificar que o raciocínio é direcionado ao padrão atual.
Holt
@ codekaizer Na verdade, eu estava me referindo a "Se definirmos o equiv (a, b) como! comp (a, b) &&! comp (b, a)" . Alterei o link para uma cotação melhor (3 linhas abaixo no padrão ...).
Holt
1
Surpreendido, ninguém mencionou ponto flutuante, onde a<be b<aambos podem ser falsos porque não são ordenados (um ou ambos NaN, o mesmo também ==é falso). Isso pode ser visto como uma espécie de equivalência. vagamente relacionado: a maxsd a, binstrução do x86 é implementada a = max(b,a) = b < a ? a : b. ( Qual é a instrução que fornece FP mínimo e máximo sem ramificação em x86? ). A instrução mantém o operando de origem (o 2º) desordenado, portanto, um loop sobre uma matriz fornecerá NaN se houver NaNs. Mas max_seen = max(max_seen, a[i])irá ignorar os NaNs.
Peter Cordes
Veja também Notas de Stepanov sobre Programação
Shafik Yaghmour
21

O ponto é qual deles deve ser retornado quando forem equivalentes; std::maxtem que retornar a(ou seja, o primeiro argumento) para este caso.

Se eles são equivalentes, retornos a.

Então a < b ? b : adeve ser usado; por outro lado, b < a ? a : b;retornará bincorretamente.

(Como disse @Holt, a citação parece oposta.)

"os dois valores são equivalentes, mas não iguais" significa que eles têm o mesmo valor quando comparados, mas podem ser objetos diferentes em alguns outros aspectos.

por exemplo

struct X { int a; int b; };
bool operator< (X lhs, X rhs) { return lhs.a < rhs.a; }
X x1 {0, 1};
X x2 {0, 2};
auto x3 = std::max(x1, x2); // it's guaranteed that an X which cantains {0, 1} is returned
songyuanyao
fonte
1
Você poderia explicar por que std::max(a, b)tem que retornar a, se ae bé equivalente?
眠りネロク
4
@ ネ ロ ク - É apenas uma escolha arbitrária por parte do padrão . Embora seja um pouco discutível se é bom.
StoryTeller - Unslander Monica
Sou apenas eu ou isso está em contradição com a pergunta? Se ae bsão equivalentes, então !(a < b) && !(b < a)é verdade, então a < be b < asão falsas, então ...?
Holt
2
@ ネ ロ ク Suponho que o padrão só queira fazê-lo determinar; quando são equivalentes, qual deles deve ser retornado.
Songyuanyao
1
obrigado por atualizar minha memória sobre por que um objeto seria "equivalente", mas não "igual".
Jeff Walker