I have two classes, A
and B
, each defining a conversion to B
. A
has a conversion operator to B
, B
has a constructor from A
. Shouldn't a call to static_cast<B>
be ambiguous? Using g++ this code compiles and chooses the conversion constructor.
#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
}
For user defined conversion sequences; there does not seem to a precedence given between the converting constructor and the conversion operator, they are both candidates;
§13.3.3.1.2/1 User-defined conversion sequences
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user- defined conversion (12.3) followed by a second standard conversion sequence. If the user-defined conversion is specified by a constructor (12.3.1), the initial standard conversion sequence converts the source type to the type required by the argument of the constructor. If the user-defined conversion is specified by a conversion function (12.3.2), the initial standard conversion sequence converts the source type to the implicit object parameter of the conversion function.
Hence if the conversion had been;
B b2 = a; // ambiguous?
It could be ambiguous and the compilation fail. Clang fails the compilation, g++ accepts the code and uses the constructor; demo code, VS also accepts the code. VS and g++ call the converting constructor (as per the OP code).
In consideration of the posted code, the user defined conversion sequences (by constructor and converting operator) and the use of static_cast
need to be considered.
§5.2.9/4 Static cast
An expression e can be explicitly converted to a type
T
using astatic_cast
of the formstatic_cast<T>(e)
if the declarationT t(e);
is well-formed, for some invented temporary variablet
(8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The expressione
is used as a glvalue if and only if the initialization uses it as a lvalue.
From the above quote, the static_cast
is equivalent to B temp(a);
and as such, the direct initialisation sequence is used.
§13.3.1.3/1 Initialization by constructor
When objects of class type are direct-initialized (8.5), copy-initialized from an expression of the same or a derived class type (8.5), or default-initialized (8.5), overload resolution selects the constructor. For direct- initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization, the candidate functions are all the converting constructors (12.3.1) of that class. The argument list is the expression-list or assignment-expression of the initialiser.
In general (excluding any constructors and operators marked as explicit
and const
concerns), given the B(const A& a);
constructor and the construction of a B
from an A
, the constructor should win since it offers the exact match when considering the best viable function; since further implicit conversions are not needed (§13.3; Overload resolution).
If the constructor B(const A& a);
was removed, the conversion (with the static_cast<>
would still succeed since the user defined conversion operator is a candidate and its use is not ambiguous.
§13.3.1.4/1 Copy-initialization of class by user-defined conversion
Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized.
Quotes are taken from the N4567 draft of the C++ standard.
It would also be instructive to invoke a user-defined conversion sequence outside just the construction of an object, i.e. calling a method.
Given the code listing (and the rules above);
#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) and VS offer different results and thus possibly different levels of compliance.
From the rules above, 1. through 3. should all succeed since the B
converting constructor is a candidate and requires no further user conversions; direct construction and copy initialisation is used for those forms. Reading from the standard (the excerpts above, in particular §13.3.3.1.2/1 and §13.3.1.4/1, and then §8.5/17.6.2), 2. and 4. could/should fail and be ambiguous - since the conversion constructor and the conversion operator are being considered with no clear ordering.
I believe this may well be an unintended use case (types being able to convert to each other in this way; there is an argument for where there would be one conversion sequence would be the general use case).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With