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 a static_cast
of the form static_cast<T>(e)
if the declaration T t(e);
is well-formed, for some invented temporary variable t
(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 expression e
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.
- clang fails 2. and 4.
- g++ accepts 1. through 4.
- VS fails 4.
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).