wake-up-neo.com

Warum verhalten sich switch und if-Anweisungen bei Konvertierungsoperatoren anders?

Warum verhalten sich switch- und if-Anweisungen bei Konvertierungsoperatoren anders?

struct WrapperA
{
    explicit operator bool() { return false; }    
};

struct WrapperB
{
    explicit operator int() { return 0; }
};

int main()
{
    WrapperA wrapper_a;
    if (wrapper_a) { /** this line compiles **/ }

    WrapperB wrapper_b;
    switch (wrapper_b) { /** this line does NOT compile **/ }
}

Der Kompilierungsfehler ist switch quantity is not an integer, während er in der Anweisung if perfekt als bool erkannt wird. (GCC)

15
nyarlathotep108

Die Syntax lautet switch ( condition ) statement mit 

bedingung - jeder Ausdruck des Integral- oder Aufzählungstyps oder eines Klassentyps kontextuellimplizit konvertierbar in einen Integral- oder Aufzählungstyp oder eine Deklaration einer einzelnen Nicht-Array-Variablen dieses Typs mit einer Klammer oder gleich Initialisierer. 

Aus cpp reference entnommen.

Dies bedeutet, dass Sie nur bei einer Ganzzahl oder einer Aufzählung Typ einen Umschaltfall ausführen können. Damit der Compiler den Wrapper implizit in den Typ "Integer/Enum" konvertieren kann, müssen Sie das explizite Schlüsselwort entfernen:

Der explizite Bezeichner gibt an, dass ein Konstruktor oder eine Konvertierungsfunktion (seit C++ 11) keine impliziten Konvertierungen zulässt

Sie können den Wrapper auch in einen int-Typ umwandeln.

Bearbeiten, um @ acraig5075 Anmerkungen zu adressieren:

Sie müssen vorsichtig sein, welcher Operator explizit und welcher implizit ist. Wenn beide implizit sind, kann der Code nicht kompiliert werden, da eine Mehrdeutigkeit vorliegt:

struct Wrapper
{
    operator int() { return 0; }
    operator bool() { return true; }    
};

source_file.cpp: In der Funktion 'int main ()': source_file.cpp: 12: 14:

fehler: Mehrdeutige Standardtypkonvertierung von 'Wrapper'

schalter (w) { 

^ source_file.cpp: 12: 14: Hinweis: Kandidatenkonvertierung

include "Wrapper :: operator int ()" und "Wrapper :: operator bool ()"

Die einzige Möglichkeit, die Mehrdeutigkeit zu beseitigen, besteht darin, eine Besetzung durchzuführen.

Wenn nur einer der Operatoren explizit ist, wird der andere für die switch-Anweisung ausgewählt: 

#include <iostream>
struct Wrapper
{
    explicit operator int() { return 0; }
    operator bool() { return true; }    
};

int main()
{
    Wrapper w;
    if (w) { /** this line compiles **/std::cout << " if is true " << std::endl; }
    switch (w) { 
        case 0:
            std::cout << "case 0" << std::endl;
            break;
        case 1:
            std::cout << "case 1" << std::endl;
            break;
    }
    return 0;
}

Ausgabe :

 if is true 
case 1

w wurde implizit in 1 (true) konvertiert (weil der Operator int explizit ist) und Fall 1 wird ausgeführt.

Auf der anderen Seite : 

struct Wrapper
{
    operator int() { return 0; }
    explicit operator bool() { return true; }    
};

Ouput:

 if is true 
case 0

w wurde implizit in 0 konvertiert, da der Operator bool explizit ist.

In beiden Fällen ist die if -Anweisung true, da wkontextuell zu einem booleschen Wert in der if-Anweisung ausgewertet wird.

17
Clonk

Ich denke das erklärt, warum die switch-Anweisung nicht akzeptiert wird, während die if-Anweisung lautet:

In den folgenden fünf Kontexten wird der Typ bool erwartet und die implizite Konvertierungssequenz wird erstellt, wenn die Deklaration bool t(e); wohlgeformt ist. Das heißt, die explizite benutzerdefinierte Konvertierungsfunktion wie explicit T::operator bool() const; wird berücksichtigt. Dieser Ausdruck e wird als kontextuell in bool konvertierbar bezeichnet.

  • kontrollierender Ausdruck von wenn, während, für;
  • die logischen Operatoren!, && und ||;
  • der bedingte Operator?:;
  • static_assert;
  • noexcept.
10
Marco Luzzara

Deklarieren eines Konvertierungsoperators explicit existiert, um implizite Konvertierungen in diesen Typ zu verhindern. Das ist ihr Zweck. switch versucht, sein Argument implizit in eine Ganzzahl zu konvertieren; Daher wird kein explicit -Operator aufgerufen. Das ist das erwartete Verhalten.

Unerwartet ist, dass ein explicit -Operator im if-Fall aufgerufen wird. Und dabei hängt eine Geschichte.

In Anbetracht der obigen Regeln besteht die Möglichkeit, einen Typ über if als testbar zu machen, darin, eine Nicht-explicit -Umwandlung in bool vorzunehmen. Die Sache ist ... bool ist ein problematischer Typ. Es ist implizit in ganze Zahlen konvertierbar. Wenn Sie also einen Typ erstellen, der implizit in bool konvertierbar ist, ist dies der gesetzliche Code:

void foo(int);
foo(convertible_type{});

Das ist aber auch sinnloser Code. Sie wollten nie, dass convertible_type implizit in eine Ganzzahl konvertiert wird. Sie wollten es nur zu Testzwecken in einen Boolean konvertieren.

Vor C++ 11 war der Weg zur Behebung des Problems das "sichere Bool-Idiom", das einen komplexen Schmerz darstellte und keinen logischen Sinn ergab (im Grunde haben Sie eine implizite Konvertierung in einen Member-Zeiger vorgenommen, der aber in einen booleschen konvertierbar war nicht auf ganze Zahlen oder reguläre Zeiger).

Wenn sie in C++ 11 explicit Konvertierungsoperatoren hinzufügten, machten sie eine Ausnahme für bool. Wenn Sie über eine explicit operator bool() verfügen, kann dieser Typ "kontextabhängig in bool" innerhalb einer festgelegten Anzahl von sprachdefinierten Stellen konvertiert werden. Dadurch kann explicit operator bool() "Testbar in booleschen Bedingungen" bedeuten.

switch benötigt keinen solchen Schutz. Wenn Sie möchten, dass ein Typ zu int-Zwecken implizit in switch konvertiert werden soll, gibt es keinen Grund, warum er auch für andere Zwecke nicht implizit in int konvertierbar ist.

3
Nicol Bolas

Eine Antwort ist, dass sich if und switch anders verhalten, weil der Standard so geschrieben wurde. Eine andere Antwort könnte darüber spekulieren, warum der Standard so geschrieben wurde. Nun, ich nehme an, dass die standard gemachten if-Anweisungen sich so verhalten, um ein bestimmtes Problem zu lösen (implizite Konvertierungen in boolwaren problematisch ), aber ich möchte eine andere Perspektive einnehmen.

In einer if-Anweisung muss die Bedingung ein boolescher Wert sein. Nicht jeder denkt über if-Anweisungen nach, vermutlich aufgrund der verschiedenen in die Sprache eingebauten Annehmlichkeiten. Im Kern muss eine if-Anweisung jedoch "do this" oder "do that" wissen. "ja oder Nein"; true oder false - d. h. ein boolescher Wert. In diesem Zusammenhang ist es ausdrücklich erforderlich, etwas in die Bedingung der Anweisung zu setzen, um das Objekt in bool zu konvertieren.

Auf der anderen Seite akzeptiert eine switch-Anweisung jeden ganzzahligen Typ. Das heißt, es gibt keinen einzigen Typ, der allen anderen vorgezogen wird. Die Verwendung einer switch kann als explizite Aufforderung zum Konvertieren eines Werts in einen ganzzahligen Typ angesehen werden, jedoch nicht unbedingt spezifisch an int. Es wird daher nicht als angemessen angesehen, eine Konvertierung in int zu verwenden, wenn diese spezifische Konvertierung explizit angefordert werden muss.

3
JaMiT

Ich glaube, der wahre Grund für dieses Verhalten hat seine Wurzeln in C, aber bevor ich das erkläre, versuche ich es in C++ zu begründen.

Eine if/while/for-Anweisung soll einen beliebigen Skalar (Integer, Float oder Pointer) oder eine in eine bool konvertierbare Klasseninstanz annehmen. Es wird lediglich geprüft, ob der Wert gleich Null ist. Bei dieser Berechtigung ist es für den Compiler relativ harmlos, einen explicit-Operator zu verwenden, um einen Wert in eine if-Anweisung einzufügen.

switch ist dagegen nur bei Ganzzahlen und enums wirklich sinnvoll. Wenn Sie versuchen, switch mit einer double oder einem Zeiger zu verwenden, wird dieselbe Fehlermeldung angezeigt. Dies erleichtert die Definition diskreter Fallwerte. Da eine switch-Anweisung speziell eine Ganzzahl anzeigen muss, ist es wahrscheinlich ein Fehler, eine Klasseninstanz zu verwenden, die keine implizite Konvertierung definiert.

Historisch ist der Grund, dass so macht es C.

C++ sollte ursprünglich abwärtskompatibel mit C sein (obwohl es noch nie erfolgreich war). C hatte bis vor kurzem nie einen booleschen Typ. Die if-Anweisungen von C mussten permissiv sein, weil es einfach keine andere Möglichkeit gab, dies zu tun. Während C keine Umwandlung von Struktur in Skalar-Typ wie C++ ausführt, spiegelt die Behandlung von explicit-Methoden in C++ die Tatsache wider, dass if-Anweisungen in beiden Sprachen sehr tolerant sind.

Die switch-Anweisung von C muss im Gegensatz zu if mit diskreten Werten arbeiten, daher kann sie nicht so tolerant sein.

0
luther

Es gibt zwei Probleme mit Ihrem Code . Erstens dürfen die Konvertierungsoperatoren nicht explizit sein, um mit switch-Anweisungen zu arbeiten .. Zweitens erfordert die switch-Bedingung einen ganzzahligen Typ, und sowohl int als auch bool sind solche Typen ist eine Mehrdeutigkeit. Wenn Sie Ihre Klasse so ändern, dass diese beiden Probleme nicht auftreten, wird switch kompiliert und wie erwartet funktionieren.

Eine andere Lösung, bei der Sie Ihre Klasse nicht ändern müssen, besteht darin, den Wert explizit in int oder bool umzuwandeln (static_cast).

0
navyblue