Todos os processos de design resultam em compromissos entre objetivos mutuamente incompatíveis. Infelizmente, o processo de design do &&
operador sobrecarregado em C ++ produziu um resultado final confuso: que o próprio recurso que você deseja &&
- seu comportamento em curto-circuito - é omitido.
Os detalhes de como esse processo de design terminou neste local infeliz, aqueles que eu não conheço. No entanto, é relevante ver como um processo de design posterior levou esse resultado desagradável em consideração. Em C #, o &&
operador sobrecarregado está em curto-circuito. Como os designers de C # conseguiram isso?
Uma das outras respostas sugere "levantamento lambda". Isso é:
A && B
poderia ser percebido como algo moralmente equivalente a:
operator_&& ( A, ()=> B )
onde o segundo argumento usa algum mecanismo para avaliação lenta, para que, quando avaliados, sejam produzidos os efeitos colaterais e o valor da expressão. A implementação do operador sobrecarregado só faria a avaliação preguiçosa quando necessário.
Não foi o que a equipe de design do C # fez. (Além disso: embora o levantamento lambda seja o que eu fiz quando chegou a hora de fazer a representação em árvore de expressão do ??
operador, o que exige que certas operações de conversão sejam executadas com preguiça. Descrever isso em detalhes seria, no entanto, uma digressão importante. Basta dizer: levantamento lambda funciona, mas é suficientemente pesado que desejávamos evitá-lo.)
Em vez disso, a solução C # divide o problema em dois problemas separados:
- devemos avaliar o operando do lado direito?
- se a resposta acima foi "sim", como combinamos os dois operandos?
Portanto, o problema é resolvido, tornando ilegal a sobrecarga &&
direta. Em vez disso, em C # você deve sobrecarregar dois operadores, cada um dos quais responde a uma dessas duas perguntas.
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 }
...
(Além disso: na verdade, três. O C # exige que, se o operador false
for fornecido, o operador true
também deve ser fornecido, o que responde à pergunta: isso é "verdadeiro?". Normalmente, não haveria razão para fornecer apenas um operador para C # requer ambos.)
Considere uma declaração do formulário:
C cresult = cleft && cright;
O compilador gera código para isso como se você tivesse escrito este pseudo-C #:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Como você pode ver, o lado esquerdo é sempre avaliado. Se for determinado como "falso-ish", será o resultado. Caso contrário, o lado direito é avaliado, e o ansioso operador definido pelo utilizador &
é invocada.
O ||
operador é definido da maneira análoga, como uma invocação do operador true e do |
operador ansioso :
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Ao definir todas as quatro operadoras - true
, false
, &
e |
- C # permite-lhe não só dizer cleft && cright
, mas também não curto-circuito cleft & cright
, e também if (cleft) if (cright) ...
, e c ? consequence : alternative
e while(c)
, e assim por diante.
Agora, eu disse que todos os processos de design são o resultado de um compromisso. Aqui, os designers de linguagem C # conseguiram um curto-circuito &&
e a correção ||
, mas isso exige sobrecarregar quatro operadores em vez de dois , o que algumas pessoas acham confuso. O recurso verdadeiro / falso do operador é um dos recursos menos compreendidos em C #. O objetivo de ter uma linguagem sensível e direta, familiar aos usuários de C ++, era contrariado pelo desejo de ter um curto-circuito e pelo desejo de não implementar o levantamento lambda ou outras formas de avaliação lenta. Penso que era uma posição de compromisso razoável, mas é importante perceber que é uma posição de compromisso. Apenas um diferente posição de compromisso do que os projetistas de C ++ chegaram.
Se o assunto do design de linguagem para esses operadores lhe interessar, considere ler minha série sobre por que o C # não define esses operadores em booleanos anuláveis:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)