2016-02-16 3 views
14

Ich habe zwei Klassen, A und B, die jeweils eine Umwandlung zu B definieren. A hat einen Konvertierungsoperator zu B, B hat einen Konstruktor von A. Sollte ein Anruf bei static_cast<B> nicht mehrdeutig sein? Mit g ++ kompiliert dieser Code und wählt den Konvertierungskonstruktor aus.Sollte dieser Code nicht zu einem mehrdeutigen Konvertierungsfehler führen?

#include<iostream> 

using namespace std; 

struct B; 
struct A { 
    A(const int& n) : x(n) {} 
    operator B() const;   //this const doesn't change the output of this code 
    int x; 
}; 

struct B{ 
    B(const double& n) : x(n) {} 
    B(const A& a); 
    double x; 
}; 

A::operator B() const   //this const doesn't change the output of this code 
{ 
    cout << "called A's conversion operator" << endl; 
    return B(double(x)); 
} 

B::B(const A& a) 
{ 
    cout << "called B's conversion constructor" << endl; 
    x = (double) a.x; 
} 

int main() { 
    A a(10); 
    static_cast<B>(a);   // prints B's conversion constructor 
} 
+0

Interessant. Ich habe den Code aktualisiert, um diese Antwort zu testen. Diese Antwort scheint zu implizieren, dass der Konvertierungsoperator aufgerufen werden sollte, während dieser Code den Konvertierungskonstruktor aufruft. – roro

+2

Ich denke, du hast Recht.Die Fragen sind ziemlich ähnlich, so dass sie wie Dupes aussehen, aber sie sind nicht. –

Antwort

13

Für benutzerdefinierte Konvertierungssequenzen; Es scheint keinen Vorrang zwischen dem Konvertierungskonstruktor und dem Konvertierungsoperator zu geben, sie sind beide Kandidaten;

§13.3.3.1.2/1 benutzerdefinierte Konvertierungssequenzen

Eine benutzerdefinierte Umwandlungsfolge besteht aus einer Anfangssequenz Standardkonvertierung durch eine benutzerdefinierte Umwandlung gefolgt (12,3), gefolgt von eine zweite Standardumwandlungssequenz. Wenn die benutzerdefinierte Konvertierung von einem Konstruktor angegeben wird (12.3.1), konvertiert die anfängliche Standardkonvertierungssequenz den Quelltyp in den Typ, der vom Argument des Konstruktors benötigt wird. Wenn die benutzerdefinierte Konvertierung durch eine Konvertierungsfunktion (12.3.2) angegeben wird, konvertiert die anfängliche Standardkonvertierungssequenz den Quelltyp in den impliziten Objektparameter der Konvertierungsfunktion.

Also, wenn die Umwandlung gewesen wäre;

B b2 = a; // ambiguous? 

Es könnte mehrdeutig sein und die Kompilierung fehlschlagen. Clang schlägt die Kompilierung fehl, g ++ akzeptiert den Code und verwendet den Konstruktor; demo code, VS akzeptiert auch den Code. VS und g ++ rufen den Konvertierungskonstruktor auf (gemäß dem OP-Code).

In Anbetracht des geposteten Codes müssen die benutzerdefinierten Konvertierungsfolgen (durch Konstruktor und Konvertierungsoperator) und die Verwendung von static_cast berücksichtigt werden.

§5.2.9/4 Static Guss

Ein Ausdruck e kann explizit in einen Typ umgewandelt werden T eine static_cast des Formulars unter Verwendung static_cast<T>(e) wenn die Erklärung T t(e); wohlgeformt ist, für einige erfunden temporäre Variable t (8.5). Die Auswirkung einer solchen expliziten Konvertierung ist die gleiche wie die Durchführung der Deklaration und Initialisierung und die anschließende Verwendung der temporären Variablen als Ergebnis der Konvertierung. Der Ausdruck e wird genau dann als glvalue verwendet, wenn die Initialisierung ihn als lvalue verwendet.

Aus dem obigen Zitat ist die static_cast entsprechen B temp(a); und als solche wird die direkte Initialisierungssequenz verwendet.

§13.3.1.3/1 Initialisierung durch Konstruktor

Wenn Objekte von Klassentyp sind Direkt initialisiert (8,5), Kopier-initialisierten aus einem Ausdruck der gleichen oder einer abgeleiteten Klasse-Typ (8,5) oder default-initialisiert (8.5), Überladungsauflösung wählt den Konstruktor.Für die Direktinitialisierung oder Standardinitialisierung sind die Kandidatenfunktionen alle Konstruktoren der Klasse des Objekts, das initialisiert wird. Für die Kopierinitialisierung sind die Kandidatenfunktionen alle Konvertierungskonstruktoren (12.3.1) dieser Klasse. Die Argumentliste ist die Ausdruckliste oder der Zuweisungsausdruck des Initialisierers.

Im Allgemeinen (mit Ausnahme von irgendwelchen Errichter und Betreiber als explicit und const Bedenken markiert), angesichts der B(const A& a); Konstruktor und den Bau eines B von einem A, sollte der Konstruktor gewinnen, da sie die genaue Übereinstimmung bietet, wenn die best viable function unter Berücksichtigung ; da weitere implicit conversions nicht benötigt werden (§13.3; Überladungsauflösung).


Wenn der Konstruktor B(const A& a); entfernt wurde, die Umwandlung (mit den static_cast<> gelingen würde nach wie vor, da der Benutzer definierte Konvertierungsoperator ein Kandidat und seine Verwendung ist nicht eindeutig.

§13.3.1.4/1 Copy-Initialisierung der Klasse von benutzerdefinierten Umwandlungs

unter den angegebenen Bedingungen in 8,5, im Rahmen eines Kopier Initialisierung eines Objekts der Klasse-Typ, eine benutzerdefinierte Umwandlung kann aufgerufen werden, um einen Initialisierungsausdruck in den Typ des Objekts zu konvertieren, das initialisiert wird.

Die Zitate stammen aus dem Entwurf N4567 des C++ - Standards.


Es wäre auch instruktiv sein, einen benutzerdefinierten Konvertierungssequenz aufzurufen außen nur die Konstruktion eines Objekts, das heißt, eine Methode aufrufen.

Angesichts der Code-Liste (und die Regeln oben);

#include <iostream> 
using namespace std; 
struct A; 
struct B { 
    B() {} 
    B(const A&) { cout << "called B's conversion constructor" << endl; } 
}; 
struct A { 
    A() {} 
    operator B() const { cout << "called A's conversion operator" << endl; return B(); } 
}; 
void func(B) {} 
int main() { 
    A a; 
    B b1 = static_cast<B>(a); // 1. cast 
    B b2 = a; // 2. copy initialise 
    B b3 (a); // 3. direct initialise 
    func(a); // 4. user defined conversion 
} 

Clang, g ++ (demo) und VS bieten unterschiedliche Ergebnisse und somit möglicherweise unterschiedliche Ebene der Compliance.

  • Klirren versagt 2. und 4.
  • g ++ 1. akzeptiert bis 4.
  • VS 4.

Aus den oben genannten Regeln nicht, 1. bis 3. sollten alle erfolgreich seit Der Konvertierungskonstruktor B ist ein Kandidat und erfordert keine weiteren Benutzerkonvertierungen. Für diese Formulare wird die direkte Konstruktions- und Kopierinitialisierung verwendet. Lesen von der Norm (die obigen Auszüge, insbesondere §13.3.3.1.2/1 und §13.3.1.4/1, und dann §8.5/17.6.2), 2. und 4. könnten/sollten versagen und mehrdeutig sein - da der Konvertierungskonstruktor und der Konvertierungsoperator ohne eindeutige Reihenfolge betrachtet werden.

Ich glaube, dass dies ein unbeabsichtigter Anwendungsfall sein kann (Typen, die sich auf diese Weise gegenseitig konvertieren können; es gibt ein Argument dafür, wo es eine Konvertierungssequenz geben würde, wäre der allgemeine Anwendungsfall).

+0

Ich denke, 2 und 4 sollten fehlschlagen, wie sie sind * copy-Initialisierung * (aber nicht von einem Ausdruck des gleichen Typs - so von [over.match.copy], nicht [over.match.ctor] abgedeckt) und dort sind zwei benutzerdefinierte Sequenzen. (Zwei benutzerdefinierte Sequenzen sind immer mehrdeutig, es sei denn, es handelt sich um identische benutzerdefinierte Konvertierungen in Kombination mit andersrangigen Standardkonvertierungen.) –

+0

@MM Möglich, aber [over.match.ctor] sagt ausdrücklich "Für die Kopierinitialisierung sind die Kandidatenfunktionen alle Konvertierungskonstruktoren (12.3.1) dieser Klasse "Ich denke, beide Interpretationen könnten angewendet werden. – Niall

+0

[over.match.ctor] sagt "copy-initialisiert von einem Ausdruck desselben oder eines abgeleiteten Klassentyps". (Der Rest des Absatzes gilt nur für die durch den ersten Satz ausgewählten Fälle) –

Verwandte Themen