¿Por qué usar "b <a? a: b "en lugar de" a <b? b: a ”para implementar la plantilla max?

154

Plantillas C ++: la guía completa, 2a edición, presenta la plantilla máxima :

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

Y explica el uso en “b < a ? a : b”lugar de “a < b ? b : a”:

Tenga en cuenta que la plantilla max () de acuerdo con [StepanovNotes] devuelve intencionalmente “b <a? a: b "en lugar de" a <b? b: a ”para garantizar que la función se comporte correctamente incluso si los dos valores son equivalentes pero no iguales.

¿Cómo entender " even if the two values are equivalent but not equal."? “a < b ? b : a”Parece tener el mismo resultado para mí.

Nan Xiao
fuente
8
Se ve mal para mí ... Las dos respuestas son "correctas", pero si ay bson equivalentes , entonces !(a < b) && !(b < a)es cierto, por lo que a < by b < ason falsas, por lo que en b < a ? a : b, bse devuelve, que no es lo que quieres ... ¿Quieres a < b ? b : a.
Holt
1
A menudo se puede distinguir entre equivalente ay bcon std::addressofet. Alabama.
Caleth
14
Si lo hace a = max(a, b);(repetidamente), es posible que no desee reemplazarlo ainnecesariamente.
Bo Persson
2
Por cierto, esta plantilla debe tomar parámetros por const-references y devolverlos por const-reference, de lo contrario, está haciendo un montón de copias inútiles (y va a anular acon una copia de a).
Holt
3
@Caleth: El tipo canónico que tiene equivalencia e igualdad es CaseInsensitiveString. Para ese tipo, ni a <A ni A <a. Pero std::addressofes irrelevante. De hecho, para lo dado T max(T a, T b)ya lo sabemos addressof(a) != addressof(b).
MSalters

Respuestas:

150

std::max(a, b)de hecho se especifica que regrese acuando los dos son equivalentes.

Eso es considerado un error por Stepanov y otros porque rompe la propiedad útil que da ay b, siempre puede ordenarlos con {min(a, b), max(a, b)}; para eso, querrás max(a, b)volver bcuando los argumentos sean equivalentes.

TC
fuente
48
Desde ese enlace "Es difícil para mí culpar a las personas que lo hacen: después de todo, simplemente siguen la especificación estándar C ++ de max escrita por mí. Me llevó varios años darme cuenta de que estaba equivocado". - ¡Guau!
Jack Aidley
23
no puedes simplemente hacer {min(a, b), max(b, a)}?
Capitán Man
12
@ CapitánMan: Sí, pero aún es menos obvio. Yo diría que tiene sentido lógico que max(a,b)devuelva a if-and-only-if min(a,b)devuelve b, y viceversa para que sean al revés y el conjunto (desordenado) {min(a,b), max(a,b)}siempre sea igual a {a,b}.
Jack Aidley
55
@ jpmc26: Si se está ordenando, por ejemplo, una lista de eventos por tiempo, no es necesario preocuparse por si la operación de clasificación es estable, sino por tener cada evento que aparece exactamente una vez en la entrada, de igual manera aparece exactamente una vez en la salida. Ciertas operaciones (como encontrar y eliminar eventos duplicados) pueden requerir el uso de un orden completo, pero en muchos otros casos, puede ser aceptable enumerar eventos simultáneos en orden arbitrario, pero no duplicarlos u omitirlos.
supercat
2
@supercat Aplicar miny maxcualquier otra cosa que no sea la marca de tiempo (la clave de clasificación) en ese escenario no tiene sentido. Los eventos (los objetos) en sí mismos ni siquiera deberían ser comparables si la igualdad no implica intercambiabilidad. La única manera {min(a, b), max(a, b)}hace ningún sentido como un mecanismo de clasificación es si los objetos son intercambiables.
jpmc26
62

Esta respuesta explica por qué el código dado es incorrecto desde un punto de vista estándar de C ++, pero está fuera de contexto.

Consulte la respuesta de @ TC para obtener una explicación contextual.


El estándar define std::max(a, b)lo siguiente [alg.min.max] (el énfasis es mío):

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

Requiere : El tipo T es menos que comparable (Tabla 18).

Devuelve : el valor más grande.

Observaciones : devuelve el primer argumento cuando los argumentos son equivalentes.

Equivalente aquí significa que !(a < b) && !(b < a)es true [alg.sorting # 7] .

En particular, si ay bson equivalentes, ambos a < by b < ason false, por lo que el valor a la derecha de :se devolverá en el operador condicional, por lo aque debe estar a la derecha, entonces:

a < b ? b : a

... parece ser la respuesta correcta. Esta es la versión utilizada por libstdc ++ y libc ++ .

Entonces, la información en su presupuesto parece incorrecta de acuerdo con el estándar actual, pero el contexto en el que se define puede ser diferente.

Bosquecillo
fuente
44
Enlace Godbolt que explica el problema (gracias @songyuanyao por la definición de X).
Holt
1
@JackAidley He editado la respuesta para especificar que el razonamiento se dirige al estándar actual.
Holt
@codekaizer En realidad me refería a "Si definimos equiv (a, b) como! comp (a, b) &&! comp (b, a)" . He cambiado el enlace a una cita mejor (3 líneas a continuación en el estándar ...).
Holt
1
Sorprendido, nadie ha mencionado el punto flotante, donde a<by b<aambos pueden ser falsos porque no están ordenados (uno o ambos NaN, también ==es falso). Eso podría verse como una especie de equivalencia. vagamente relacionado: maxsd a, bimplementos de instrucciones de x86 a = max(b,a) = b < a ? a : b. ( ¿Cuál es la instrucción que proporciona FP min y max sin ramificación en x86? ). La instrucción mantiene el operando de origen (el segundo) en desordenado, por lo que un bucle sobre una matriz le dará NaN si hubiera NaN. Pero max_seen = max(max_seen, a[i])ignorará los NaNs.
Peter Cordes
Ver también las Notas de Stepanov sobre Programación
Shafik Yaghmour
21

El punto es cuál debería devolverse cuando son equivalentes; std::maxtiene que volver a(es decir, el primer argumento) para este caso.

Si son equivalentes, devuelve a.

Por a < b ? b : alo tanto, debe ser utilizado; Por otro lado, b < a ? a : b;volverá bincorrectamente.

(Como dijo @Holt, la cita parece opuesta).

"los dos valores son equivalentes pero no iguales" significa que tienen el mismo valor cuando se comparan, pero migran como objetos diferentes en otros aspectos.

p.ej

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
fuente
1
¿Podría explicar con más detalle por qué std::max(a, b)tiene que volver a, si ay bson equivalentes?
眠 り ネ ロ ク
44
@ ネ ロ ク - Es solo una elección arbitraria por parte del estándar . Aunque es un poco discutible si es bueno.
StoryTeller - Unslander Monica
¿Soy solo yo o está en contradicción con la pregunta? Si ay bson equivalentes, entonces !(a < b) && !(b < a)es verdadero, entonces a < by b < ason falsos, entonces ...?
Holt
2
@ ネ ロ ク Supongo que el estándar solo quiere determinarlo; cuando son equivalentes cuál debería ser devuelto.
songyuanyao
1
gracias por actualizar mi memoria sobre por qué un objeto sería "equivalente" pero no "igual".
Jeff Walker