Todos los procesos de diseño resultan en compromisos entre objetivos mutuamente incompatibles. Desafortunadamente, el proceso de diseño para el &&
operador sobrecargado en C ++ produjo un resultado final confuso: que &&
se omite la característica que desea , su comportamiento de cortocircuito.
Los detalles de cómo ese proceso de diseño terminó en este desafortunado lugar, aquellos que no conozco. Sin embargo, es relevante ver cómo un proceso de diseño posterior tuvo en cuenta este resultado desagradable. En C #, el &&
operador sobrecargado está en cortocircuito. ¿Cómo lograron eso los diseñadores de C #?
Una de las otras respuestas sugiere "levantamiento lambda". Es decir:
A && B
podría realizarse como algo moralmente equivalente a:
operator_&& ( A, ()=> B )
donde el segundo argumento usa algún mecanismo para la evaluación diferida, de modo que cuando se evalúa, se producen los efectos secundarios y el valor de la expresión. La implementación del operador sobrecargado solo haría la evaluación diferida cuando sea necesario.
Esto no es lo que hizo el equipo de diseño de C #. (Aparte: aunque el levantamiento de lambda es lo que hice cuando llegó el momento de hacer una representación del ??
operador del árbol de expresión , lo que requiere que ciertas operaciones de conversión se realicen con pereza. Sin embargo, describir eso en detalle sería una digresión importante. Basta decir: levantamiento de lambda funciona pero es lo suficientemente pesado como para desear evitarlo).
Por el contrario, la solución C # divide el problema en dos problemas separados:
- ¿Deberíamos evaluar el operando de la derecha?
- si la respuesta a lo anterior fue "sí", entonces, ¿cómo combinamos los dos operandos?
Por lo tanto, el problema se resuelve al hacer ilegal la sobrecarga &&
directa. Por el contrario, en C # debe sobrecargar dos operadores, cada uno de los cuales responde a una de esas dos preguntas.
class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(Aparte: en realidad, tres. C # requiere que si false
se proporciona el operador true
, también se debe proporcionar el operador , lo que responde a la pregunta: ¿esto es "verdadero-ish?" requiere ambos.)
Considere una declaración de la forma:
C cresult = cleft && cright;
El compilador genera código para esto como creía que había escrito este pseudo-C #:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Como puede ver, el lado izquierdo siempre se evalúa. Si se determina que es "falso-ish", entonces es el resultado. De lo contrario, se evalúa el lado derecho y se invoca al operador ansioso definido por el usuario &
.
El ||
operador se define de manera análoga, como una invocación del operador verdadero y el |
operador ansioso :
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Mediante la definición de los cuatro operadores - true
, false
, &
y |
- C # le permite no sólo de hablar cleft && cright
, sino también no cortocircuitos cleft & cright
, y también if (cleft) if (cright) ...
, y c ? consequence : alternative
, y while(c)
, y así sucesivamente.
Ahora, dije que todos los procesos de diseño son el resultado de un compromiso. Aquí los diseñadores del lenguaje C # consiguieron un cortocircuito &&
y la ||
derecha, pero hacerlo requiere una sobrecarga cuatro operadores en lugar de dos , que algunas personas encuentran problemas. La función verdadero / falso del operador es una de las características menos conocidas de C #. El objetivo de tener un lenguaje sensible y directo que sea familiar para los usuarios de C ++ se opuso a los deseos de tener un corto circuito y el deseo de no implementar el levantamiento lambda u otras formas de evaluación perezosa. Creo que fue una posición de compromiso razonable, pero es importante darse cuenta de que es una posición de compromiso. Solo un diferente posición de compromiso que los diseñadores de C ++ aterrizaron.
Si el tema del diseño del lenguaje para tales operadores le interesa, considere leer mi serie sobre por qué C # no define estos operadores en booleanos que aceptan valores NULL:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)