Rozważmy następujący krótki program w C ++:
#include <iostream>
class B {
public:
operator bool() const {
return false;
}
};
class B2 : public B {
public:
operator int() {
return 5;
}
};
int main() {
B2 b;
std::cout << std::boolalpha << (bool)b << std::endl;
}
Jeśli skompiluję go na różnych kompilatorach, otrzymam różne wyniki. Z Clang 3.4 i GCC 4.4.7 drukuje true
, podczas gdy Visual Studio 2013 drukuje false
, co oznacza, że wywołują różne operatory rzutowania pod adresem (bool)b
. Jakie jest prawidłowe zachowanie zgodnie z normą?
W moim rozumieniu operator bool()
nie potrzebuje nawrócenia, podczas gdy operator int()
wymagałoby int
do bool
konwersji, więc kompilator powinien wybrać pierwszy. Czy const
coś z tym robi, czy konwersja const jest uważana przez kompilator za bardziej „kosztowną”?
Jeśli usunę plik const
, wszystkie kompilatory w równym stopniu produkują false
jako wyjście. Z drugiej strony, jeśli połączę te dwie klasy razem (oba operatory będą w tej samej klasie), wszystkie trzy kompilatory wygenerują true
wynik.
źródło
const
jest tutaj kluczem. Oba operatory są jednakowo stałe lub nie, zachowują się zgodnie z oczekiwaniami,bool
wersja "wygrywa". Z różną stałością, zawsze wersja bez stałej „wygrywa” na każdej platformie. Z klasami pochodnymi i różnymi stałościami operatorów, VS wydaje się mieć błąd, ponieważ zachowuje się inaczej niż dwa pozostałe kompilatory.true
. Jeśli GCC, Clang i EDG zgadzają się, a MSVC nie zgadza się, zwykle oznacza to, że MSVC się myli.Odpowiedzi:
Norma stwierdza:
Co oznacza, że
operator bool
nie jest to ukryte przezoperator int
.Norma stwierdza:
„Domniemany argument obiektu” w tym przypadku to
b
, który jest typuB2 &
.operator bool
wymagaconst B2 &
, więc kompilator będzie musiał dodać const tob
do wywołaniaoperator bool
. To - wszystkie inne rzeczy są równe -operator int
lepiej pasuje.Standard stwierdza, że a
static_cast
(który wykonuje rzutowanie w stylu C w tym przypadku) może przekonwertować na typT
(w tym przypadkuint
), jeśli:Dlatego
int
można przekonwertować na abool
, abool
można również przekształcić w abool
.Norma stwierdza:
Więc zestaw przeciążenia składa się z
operator int
ioperator bool
. Wszystkie inne rzeczy są równe,operator int
to lepsze dopasowanie (ponieważ nie musisz dodawać stałości). Dlategooperator int
należy wybrać.Zauważ, że (być może wbrew intuicji) standard nie bierze pod uwagę zwracanego typu (tj. Typu, na który te operatory konwertują) po ich dodaniu do zbioru przeciążeń (jak ustalono powyżej), pod warunkiem, że sekwencja konwersji dla argumentów jednego z są one lepsze od kolejności konwersji dla argumentów drugiego (co ze względu na stałość ma miejsce w tym przypadku).
Norma stwierdza:
W tym przypadku jest tylko jeden argument (niejawny
this
parametr). Sekwencja konwersji dlaB2 &
=>B2 &
(do wywołaniaoperator int
) jest lepsza niżB2 &
=>const B2 &
(do wywołaniaoperator bool
) i dlategooperator int
jest wybierana z zestawu przeciążenia bez względu na fakt, że w rzeczywistości nie jest ona konwertowana bezpośrednio nabool
.źródło
Krótki
Funkcja konwersji
operator int()
jest wybierana przez clang over,operator bool() const
ponieważb
nie jest kwalifikowana jako const, podczas gdy operator konwersji dla bool to.Krótki rozumowanie jest to, że funkcje kandydujących dla rozdzielczości przeciążenia (z utajonego parametru obiektu w miejscu), do konwersji
b
dobool
sąoperator bool (B2 const &); operator int (B2 &);
gdzie drugi jest lepiej dopasowany, ponieważ
b
nie jest określony jako const.Jeśli obie funkcje mają tę samą kwalifikację (obie
const
lub nie),operator bool
jest wybierana, ponieważ zapewnia bezpośrednią konwersję.Konwersja za pomocą notacji rzutowej, analizowana krok po kroku
Jeśli zgodzimy się, że boolean ostream inserter (std :: basic_ostream :: operator << (bool val) zgodnie z [ostream.inserters.arithmetic]) jest wywoływany z wartością wynikającą z konwersji
b
nabool
to możemy wykopać się do tej konwersji .1. Wyrażenie obsady
Obsada b do bool
(bool)b
ocenia do
static_cast<bool>(b)
zgodnie z C ++ 11, 5.4 / 4 [wyraż.cast], ponieważ
const_cast
nie ma zastosowania (nie można tu dodawać ani usuwać stałej).Ta statyczna konwersja jest dozwolona według C ++ 11, 5.2.9 / 4 [expr.static.cast] , jeśli
bool t(b);
dla wymyślonej zmiennej t jest dobrze sformułowana. Takie instrukcje nazywane są bezpośrednią inicjalizacją zgodnie z C ++ 11, 8.5 / 15 [dcl.init] .2. Inicjalizacja bezpośrednia
bool t(b);
Klauzula 16 najmniej wymienionego standardowego akapitu stwierdza (moje wyróżnienie):
2.1 Jakie funkcje konwersji są dostępne?
Dostępne funkcje konwersji to,
operator int ()
aoperator bool() const
ponieważ C ++ 11, 12.3 / 5 [class.conv] mówi nam:Podczas gdy C ++ 11, 13.3.1.5/1 [over.match.conv] stwierdza:
gdzie S jest klasą, z której zostanie przekonwertowana.
2.2 Które funkcje konwersji mają zastosowanie?
C ++ 11, 13.3.1.5/1 [over.match.conv] (moje podkreślenie):
Dlatego
operator bool () const
ma zastosowanie, ponieważ nie jest ukryty wB2
środku i dajebool
.Część z naciskiem w ostatnim cytacie standardowym ma znaczenie dla konwersji przy użyciu,
operator int ()
ponieważint
jest typem, który można przekonwertować na bool za pomocą standardowej sekwencji konwersji. Konwersja zint
nabool
nie jest nawet sekwencją, ale zwykłą konwersją bezpośrednią, która jest dozwolona w C ++ 11, 4.12 / 1 [conv.bool]Oznacza to, że
operator int ()
ma to również zastosowanie.2.3 Która funkcja konwersji jest wybrana?
Wybór odpowiedniej funkcji konwersji odbywa się poprzez rozwiązanie przeciążenia ( C ++ 11, 13.3.1.5/1 [over.match.conv] ):
Istnieje jeden specjalny „dziwactwo”, jeśli chodzi o rozwiązywanie przeciążeń funkcji składowych klasy: niejawny parametr obiektu ”.
Per C ++ 11, 13.3.1 [over.match.funcs] ,
gdzie typ tego parametru dla niestatycznych funkcji składowych - zgodnie z klauzulą 4 - to:
Oznacza to, że (na C ++ 11, 13.3.1.5/2 [over.match.conv] ), podczas inicjalizacji przez funkcję konwersji,
Kandydujące funkcje do rozwiązywania problemów z przeciążeniem to:
operator bool (B2 const &); operator int (B2 &);
Oczywiście
operator int ()
jest to lepsze dopasowanie, jeśli zażądano konwersji przy użyciu niestałego obiektu typu,B2
ponieważoperator bool ()
wymagana jest konwersja kwalifikacji.Jeśli obie funkcje konwersji mają tę samą kwalifikację const, rozwiązanie przeciążenia tych funkcji już nie wystarczy. W takim przypadku ma miejsce ranking konwersji (kolejności).
3. Dlaczego jest
operator bool ()
wybierany, skoro obie funkcje konwersji mają tę samą kwalifikację const?Konwersja z
B2
dobool
jest sekwencją konwersji zdefiniowaną przez użytkownika ( C ++ 11, 13.3.3.1.2 / 1 [over.ics.user] )C ++ 11, 13.3.3.2/3 [over.ics.rank]
Drugi średnia konwersji przypadku
operator bool()
jestbool
dobool
(konwersja tożsamości), podczas gdy drugi standard konwersji w przypadkuoperator int ()
jestint
dobool
których to logiczna konwersji.Dlatego sekwencja konwersji przy użyciu
operator bool ()
jest lepsza, jeśli obie funkcje konwersji mają tę samą kwalifikację const.źródło
a/b
jest taki sam, niezależnie od tego, czy kod jestfloat f = a/b;
czyint f = a/b;
. Ale to przypadek, w którym może to być nieco zaskakujące.Typ bool w C ++ ma dwie wartości - prawda i fałsz z odpowiadającymi im wartościami 1 i 0. Nieodłączne zamieszanie można uniknąć, jeśli dodasz operator bool w klasie B2, który jawnie wywołuje operator bool klasy bazowej (B), a następnie pojawi się wynik jako fałszywe. Oto mój zmodyfikowany program. Wtedy operator bool oznacza operator bool, a nie operator int w jakikolwiek sposób.
#include <iostream> class B { public: operator bool() const { return false; } }; class B2 : public B { public: operator int() { return 5; } operator bool() { return B::operator bool(); } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; }
W twoim przykładzie (bool) b próbował wywołać operator bool dla B2, B2 odziedziczył operator bool, a operator int, na podstawie reguły dominacji, zostaje wywołany operator int, a dziedziczony operator bool w B2. Jednak dzięki jawnemu umieszczeniu operatora bool w samej klasie B2 problem zostanie rozwiązany.
źródło
true and false with corresponding values 1 and 0
To nadmierne uproszczenie.true
konwertuje na liczbę całkowitą1
, ale to nie znaczy, że „ma” wartość1
. Rzeczywiście,true
może być42
pod spodem.bool
nigdy nie ma wartości 0 ani 1; jedyne wartości, jakie może przyjąć, tofalse
itrue
(które są konwertowane na 0 i 1, jeślibool
jest konwertowany na typ całkowity).Niektóre z poprzednich odpowiedzi zawierają już wiele informacji.
Mój wkład jest taki, że „operacje rzutowania” są kompilowane podobnie do „operacji przeciążonych”, proponuję utworzyć funkcję z unikalnym identyfikatorem dla każdej operacji, a później zastąpić ją wymaganym operatorem lub rzutowaniem.
#include <iostream> class B { public: bool ToBool() const { return false; } }; class B2 : public B { public: int ToInt() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << b.ToBool() << std::endl; }
A później zastosuj operator lub rzut.
#include <iostream> class B { public: operator bool() { return false; } }; class B2 : public B { public: operator int() { return 5; } }; int main() { B2 b; std::cout << std::boolalpha << (bool)b << std::endl; }
Tylko moje 2 centy.
źródło