Por que c++ não tem &&= ou ||= para booleanos?
-
21-09-2019 - |
Pergunta
Tem alguma "coisa muito ruim" que pode acontecer &&= e ||= foram usados como açúcar sintático para bool foo = foo && bar
e bool foo = foo || bar
?
Solução
A bool
só pode ser true
ou false
em C++.Como tal, usando &=
e |=
é relativamente seguro (embora eu não goste particularmente da notação).É verdade que eles executarão operações de bits em vez de operações lógicas (e, portanto, não causarão curto-circuito), mas essas operações de bits seguem um mapeamento bem definido, que é efetivamente equivalente às operações lógicas, desde que ambos os operandos são do tipo bool
.1
Ao contrário do que outras pessoas disseram aqui, um bool
em C++ nunca deve ter um valor diferente, como 2
.Ao atribuir esse valor a um bool
, ele será convertido para true
conforme o padrão.
A única maneira de colocar um valor inválido em um bool
é usando reinterpret_cast
em ponteiros:
int i = 2;
bool b = *reinterpret_cast<bool*>(&i);
b |= true; // MAY yield 3 (but doesn’t on my PC!)
Mas como esse código resulta em comportamento indefinido de qualquer maneira, podemos ignorar com segurança esse problema potencial na conformidade do código C++.
1 É certo que esta é uma advertência bastante grande, como ilustra o comentário de Angew:
bool b = true;
b &= 2; // yields `false`.
A razão é que b & 2
realiza promoção inteira de modo que a expressão seja então equivalente a static_cast<int>(b) & 2
, o que resulta em 0
, que é então convertido novamente em um bool
.Então é verdade que a existência de um operator &&=
melhoraria a segurança do tipo.
Outras dicas
&&
e &
têm semântica diferente: &&
não avaliará o segundo operando se o primeiro operando for false
.ou sejaalgo como
flag = (ptr != NULL) && (ptr->member > 3);
é seguro, mas
flag = (ptr != NULL) & (ptr->member > 3);
não é, embora ambos os operandos sejam do tipo bool
.
O mesmo é verdade para &=
e |=
:
flag = CheckFileExists();
flag = flag && CheckFileReadable();
flag = flag && CheckFileContents();
se comportará de maneira diferente de:
flag = CheckFileExists();
flag &= CheckFileReadable();
flag &= CheckFileContents();
Resposta curta
Todos os operadores +=
, -=
, *=
, /=
, &=
, |=
...são aritméticos e fornecem a mesma expectativa:
x &= foo() // We expect foo() be called whatever the value of x
No entanto, os operadores &&=
e ||=
seria lógico, e esses operadores podem estar sujeitos a erros porque muitos desenvolvedores esperariam foo()
ser sempre chamado x &&= foo()
.
bool x;
// ...
x &&= foo(); // Many developers might be confused
x = x && foo(); // Still confusing but correct
x = x ? foo() : x; // Understandable
x = x ? foo() : false; // Understandable
if (x) x = foo(); // Obvious
Será que realmente precisamos tornar o C/C++ ainda mais complexo para obter um atalho para
x = x && foo()
?Queremos realmente ofuscar ainda mais a declaração enigmática
x = x && foo()
?
Ou queremos escrever um código significativo comoif (x) x = foo();
?
Resposta longa
Exemplo para &&=
Se &&=
operador estava disponível, então este código:
bool ok = true; //becomes false when at least a function returns false
ok &&= f1();
ok &&= f2(); //we may expect f2() is called whatever the f1() returned value
é equivalente a:
bool ok = true;
if (ok) ok = f1();
if (ok) ok = f2(); //f2() is called only when f1() returns true
Este primeiro código é sujeito a erros porque muitos desenvolvedores pensariam f2()
é sempre chamado de qualquer que seja o nome f1()
valor retornado.É como escrever bool ok = f1() && f2();
onde f2()
é chamado apenas quando f1()
retorna true
.
- Se o desenvolvedor realmente quiser
f2()
ser chamado somente quandof1()
retornatrue
, portanto, o segundo código acima é menos sujeito a erros. - Caso contrário (o desenvolvedor quer
f2()
ser sempre chamado),&=
é suficiente:
Exemplo para &=
bool ok = true;
ok &= f1();
ok &= f2(); //f2() always called whatever the f1() returned value
Além disso, é mais fácil para o compilador otimizar o código acima do que o código abaixo:
bool ok = true;
if (!f1()) ok = false;
if (!f2()) ok = false; //f2() always called
Comparar &&
e &
Podemos perguntar-nos se os operadores &&
e &
dar o mesmo resultado quando aplicado em bool
valores?
Vamos verificar usando o seguinte código C++:
#include <iostream>
void test (int testnumber, bool a, bool b)
{
std::cout << testnumber <<") a="<< a <<" and b="<< b <<"\n"
"a && b = "<< (a && b) <<"\n"
"a & b = "<< (a & b) <<"\n"
"======================" "\n";
}
int main ()
{
test (1, true, true);
test (2, true, false);
test (3, false, false);
test (4, false, true);
}
Saída:
1) a=1 and b=1
a && b = 1
a & b = 1
======================
2) a=1 and b=0
a && b = 0
a & b = 0
======================
3) a=0 and b=0
a && b = 0
a & b = 0
======================
4) a=0 and b=1
a && b = 0
a & b = 0
======================
Conclusão
Portanto SIM podemos substituir &&
por &
para bool
valores ;-)
Então é melhor usar &=
em vez de &&=
.
Podemos considerar &&=
tão inútil para booleanos.
O mesmo para ||=
operador
|=
também é menos sujeito a erros que||=
Se um desenvolvedor quiser f2()
ser chamado somente quando f1()
retorna false
, em vez de:
bool ok = false;
ok ||= f1();
ok ||= f2(); //f2() is called only when f1() returns false
ok ||= f3(); //f3() is called only when f1() or f2() return false
ok ||= f4(); //f4() is called only when ...
Aconselho a seguinte alternativa mais compreensível:
bool ok = false;
if (!ok) ok = f1();
if (!ok) ok = f2();
if (!ok) ok = f3();
if (!ok) ok = f4();
// no comment required here (code is enough understandable)
ou se preferir tudo em uma linha estilo:
// this comment is required to explain to developers that
// f2() is called only when f1() returns false, and so on...
bool ok = f1() || f2() || f3() || f4();