Dlaczego c ++ nie ma && = lub || = dla wartości logicznych?

126

Czy jest „bardzo zła rzecz”, która może się zdarzyć &&=i ||=została użyta jako cukier syntaktyczny do bool foo = foo && bari bool foo = foo || bar?

Kache
źródło
5
Zobacz to inne pytanie: stackoverflow.com/questions/2324549/… To dotyczy Javy, ale dzieląc linię C, stosuje się głównie te same argumenty.
jamesdlin
Zasadniczo, po prostu c ++ nie ma tego b / c, w którym go nie umieścili - ma to w językach takich jak Ruby. boo ...
Kache
2
Ale czy w Rubim nie jest z x ||= ygrubsza odpowiednikiem C ++ x = x ? x : y;dla żadnego typu? Innymi słowy, „ustaw na y, jeśli jeszcze nie zostało ustawione”. Jest to znacznie bardziej przydatne niż C czy C ++ x ||= y, które (z wyjątkiem przeciążania operatorów) spowodowałyby „ustaw x na, (bool)ychyba że już ustawione”. Nie chcę do tego dodawać kolejnego operatora, wydaje się to trochę słabe. Po prostu napisz if (!x) x = (bool)y. Ale tak naprawdę nie używam boolzmiennych na tyle, by chcieć dodatkowych operatorów, które są naprawdę przydatne tylko w tym jednym typie.
Steve Jessop
1
Jestem pewien, że głównym powodem, dla którego C ++ nie ma &&=lub ||=jest po prostu to, że C ich nie ma. Jestem całkiem pewien, że powodem, dla którego C ich nie ma, jest to, że funkcjonalność nie została uznana za wystarczająco korzystną.
Jonathan Leffler
2
Ponadto, będąc ultrapedantycznym, notacja bool foo = foo || bar;wywoływałaby niezdefiniowane zachowanie, ponieważ foonie została zainicjowana przed oceną foo || bar. Oczywiście ma to być coś podobnego, bool foo = …initialization…; …; foo = foo || bar;a pytanie jest wtedy aktualne.
Jonathan Leffler

Odpowiedzi:

73

A boolmoże być tylko truelub falsew C ++. W związku z tym używanie &=i |=jest stosunkowo bezpieczne (chociaż nie podoba mi się ten zapis). To prawda, że ​​będą wykonywać operacje bitowe, a nie operacje logiczne (i dlatego nie będą powodować zwarcia), ale te operacje bitowe są zgodne z dobrze zdefiniowanym odwzorowaniem, które jest skutecznie równoważne operacjom logicznym, o ile oba operandy są typubool . 1

W przeciwieństwie do tego, co powiedzieli inni ludzie, a boolw C ++ nigdy nie może mieć innej wartości, takiej jak 2. Podczas przypisywania tej wartości do a bool, zostanie ona przekonwertowana truezgodnie ze standardem.

Jedynym sposobem uzyskania nieprawidłowej wartości w a booljest użycie reinterpret_castwskaźników:

int i = 2;
bool b = *reinterpret_cast<bool*>(&i);
b |= true; // MAY yield 3 (but doesn’t on my PC!)

Ale ponieważ ten kod i tak skutkuje niezdefiniowanym zachowaniem, możemy bezpiecznie zignorować ten potencjalny problem w dostosowaniu kodu C ++.


1 Trzeba przyznać, że jest to dość duże zastrzeżenie, co ilustruje komentarz Angew:

bool b = true;
b &= 2; // yields `false`.

Powodem jest to, że b & 2przeprowadza promocję liczb całkowitych w taki sposób, że wyrażenie jest następnie równoważne static_cast<int>(b) & 2, co powoduje 0, że jest następnie konwertowany z powrotem na bool. Więc to prawda, że ​​istnienie operator &&=litery poprawiłoby bezpieczeństwo typu.

Konrad Rudolph
źródło
4
Ale && i || operatorzy będą pracować na wszystkim, co konwertuje na bool, a nie tylko na bool.
dan04
13
Nie robią tego samego, nawet na boolach. ||i &&skrót, tj. drugi argument nie jest operandem, jeśli pierwszy argument to true(odpowiednio falsedla &&). |, &, |=I &=zawsze oceniać oba operandy.
Niki,
4
to nie odpowiada na pytanie, dlaczego && = i || = nie są operatorami c ++.
thang
9
To nie jest bezpieczne w użyciu &=dla lewej strony typu bool, bo to zupełnie możliwe, że po prawej stronie, aby być typu innego niż bool(jak islowerlub innej funkcji stdlib C, która powraca niezerowe dla prawdziwej wartości). Gdybyśmy mieli hipotezę &&=, prawdopodobnie zmusiłoby to prawą stronę do przejścia na bool, co &=nie. Innymi słowy, bool b = true; b &= 2;powoduje b == false.
Angew nie jest już dumny z
2
@Antonio Twardy tłum. 😝
Konrad Rudolph
44

&&i &mają inną semantykę: &&nie oceni drugiego operandu, jeśli pierwszy operand jest false. czyli coś w stylu

flag = (ptr != NULL) && (ptr->member > 3);

jest bezpieczny, ale

flag = (ptr != NULL) & (ptr->member > 3);

nie jest, chociaż oba operandy są typu bool.

To samo dotyczy &=i |=:

flag = CheckFileExists();
flag = flag && CheckFileReadable();
flag = flag && CheckFileContents();

zachowa się inaczej niż:

flag = CheckFileExists();
flag &= CheckFileReadable();
flag &= CheckFileContents();
Niki
źródło
35
tym bardziej warto mieć && = moim zdaniem. = P
Kache
4
To nie jest jednak tak naprawdę odpowiedź.
Catskul,
27

Krótka odpowiedź

Wszyscy operatorzy +=, -=, *=, /=, &=, |=... są arytmetyczny i zapewnić taką samą oczekiwania:

x &= foo()  // We expect foo() be called whatever the value of x

Jednak operatory &&=i ||=byłyby logiczne, a operatory te mogą być podatne na błędy, ponieważ wielu programistów spodziewa foo()się, że będą zawsze wywoływani 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
  • Czy naprawdę musimy uczynić C / C ++ jeszcze bardziej złożonym, aby uzyskać skrót x = x && foo()?

  • Czy naprawdę chcemy bardziej zaciemnić tajemnicze stwierdzenie x = x && foo()?
    A może chcemy napisać znaczący kod if (x) x = foo();?


Długa odpowiedź

Przykład dla &&=

Jeśli &&=operator był dostępny, to ten kod:

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

jest równa:

bool ok = true;
if (ok) ok = f1();
if (ok) ok = f2(); //f2() is called only when f1() returns true

Ten pierwszy kod jest podatny na błędy, ponieważ wielu programistów pomyślałoby, że f2()jest zawsze wywoływany niezależnie od f1()zwracanej wartości. To tak, jakby pisać, bool ok = f1() && f2();gdzie f2()jest wywoływane tylko wtedy, gdy f1()zwraca true.

  • Jeśli programista faktycznie chce, f2()aby wywoływano go tylko wtedy , gdy f1()zwraca true, drugi kod powyżej jest mniej podatny na błędy.
  • W przeciwnym razie (programista chce, f2()aby zawsze dzwoniono), &=wystarczy:

Przykład dla &=

bool ok = true;
ok &= f1();
ok &= f2(); //f2() always called whatever the f1() returned value

Co więcej, kompilatorowi łatwiej jest zoptymalizować powyższy kod niż poniższy:

bool ok = true;
if (!f1())  ok = false;
if (!f2())  ok = false;  //f2() always called

Porównaj &&i&

Możemy się zastanawiać, czy operatory &&i &dają taki sam wynik, gdy są stosowane na boolwartościach?

Sprawdźmy, używając następującego kodu 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);
}

Wynik:

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
======================

Wniosek

Dlatego TAK możemy zastąpić &&przez &do boolwartości ;-)
Więc lepiej używać &=zamiast &&=.
Możemy uznać &&=za bezużyteczne dla wartości logicznych.

To samo dotyczy ||=

operator |=jest również mniej podatny na błędy niż||=

Jeśli programista chce f2()być wywoływany tylko wtedy , gdy f1()zwraca false, zamiast:

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 ...

Radzę następującą, bardziej zrozumiałą alternatywę:

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)

lub jeśli wolisz wszystko w jednym stylu:

// 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();
olibre
źródło
13
A jeśli rzeczywiście chcę takiego zachowania? Że wyrażenie prawej ręki nie zostanie wykonane, jeśli wyrażenie lewej ręki jest błędne. Irytujące jest zapisywanie zmiennych dwa razy, na przykładsuccess = success && DoImportantStuff()
Niklas R
1
Radzę pisać if(success) success = DoImportantStuff(). Gdyby zeznanie success &&= DoImportantStuff()było dozwolone, wielu programistów pomyślałoby, że DoImportantStuff()zawsze jest wywoływane niezależnie od wartości success. Mam nadzieję, że ta odpowiedź jest odpowiedzią na Twoje wątpliwości ... Poprawiłem także wiele części mojej odpowiedzi. Proszę, powiedz mi, czy moja odpowiedź jest teraz bardziej zrozumiała? (o celu komentarza) Do zobaczenia ;-)
olibre
9
„Gdyby stwierdzenie success &&= DoImportantStuff()było dozwolone, wielu programistów pomyślałoby, że DoImportantStuff()zawsze nazywa się je bez względu na wartość sukcesu”. Można to jednak powiedzieć if (success && DoImportantStuff()). O ile pamiętają logikę składni if, z którą nie powinni mieć problemu &&=.
pilkch
2
Nie rozumiem, jak ludzie mogą zakładać, że f1()zawsze ocenia się w, ok &&= f(1) ale nie zakładam, że zawsze ocenia się w ok = ok && f(1). Wydaje mi się to równie prawdopodobne.
einpoklum
1
Właściwie spodziewam v1 += e2się, że będę odpowiednikiem cukru syntaktycznego v1 = v1 + e1dla zmiennej v1 i wyrażenia e2. Tylko skrótowa notacja, to wszystko.
einpoklum,