Conversion Constructor Vs. Conversion Operator: Precedence

Conversion constructor vs. conversion operator: precedence

You do copy initialization, and the candidate functions that are considered to do the conversions in the conversion sequence are conversion functions and converting constructors. These are in your case

B(const A&)
operator B()

Now, that are the way you declare them. Overload resolution abstracts away from that, and transforms each candidate into a list of parameters that correspond to the arguments of the call. The parameters are

B(const A&)
B(A&)

The second one is because the conversion function is a member function. The A& is the so-called implicit object parameter that's generated when a candidate is a member function. Now, the argument has type A. When binding the implicit object parameter, a non-const reference can bind to an rvalue. So, another rule says that when you have two viable functions whose parameters are references, then the candidate having the fewest const qualification will win. That's why your conversion function wins. Try making operator B a const member function. You will notice an ambiguity.

From an object-oriented philosophical standpoint, is this the way the code should behave? Who knows more about how an A object should become a B object, A or B? According to C++, the answer is A -- is there anything in object-oriented practice that suggests this should be the case? To me personally, it would make sense either way, so I'm interested to know how the choice was made.

For the record, if you make the conversion function a const member function, then GCC will chose the constructor (so GCC seems to think that B has more business with it?). Switch to pedantic mode (-pedantic) to make it cause a diagnostic.


Standardese, 8.5/14

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3).

And 13.3.1.4

Overload resolution is used to select the user-defined conversion to be invoked. Assuming that "cv1 T" is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:

  • The converting constructors (12.3.1) of T are candidate functions.
  • When the type of the initializer expression is a class type "cv S", the conversion functions of S and its base classes are considered. Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. Conversion functions that return "reference to X" return lvalues of type X and are therefore considered to yield X for this process of selecting candidate functions.

In both cases, the argument list has one argument, which is the initializer expression. [Note: this argument will be compared against the first parameter of the constructors and against the implicit object parameter of the conversion functions. ]

And 13.3.3.2/3

  • Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if [...] S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

Which gets precedence when Converting Constructor as well as Conversion Operator are defined and why compilers differ this conversion?

Which gets precedence when Converting Constructor as well as Conversion Operator are defined

Neither.

why compilers differ this conversion?

If the conversion sequence is ambiguous, then the program is ill formed. Thus the compiler is allowed to not produce a program, and required to diagnose the issue. Allowing such ambiguous conversion intentionally would be considered a language extension, but failing to diagnose regardless of the extension would be a failure to conform to the standard.

If the conversion sequence is unambiguous, and there is no other reason for the program to be ill formed, then a compiler that refuses to compile it does not conform to the standard.


Edit: Regarding the added example: The conversion of myClass mc = oc is indeed ambiguous and the program is ill-formed. So, a possible reason for difference in behaviour is either a language extension of the compiler that allows it, or a compiler bug. If it is not diagnosed, then the compiler does not conform to the standard. I recommend disabling language extensions.

myClass mc1 = otherClass() is well-formed because there is only one valid candidate for the conversion. The converting constructor is not a valid candidate because lvalue references to non-const cannot be bound to rvalues.

C++ conversion operator vs constructor precedence, compilers differ

If your compiler supports C++11 features you can declare your conversion operators explicit:

class ClassB
{
public:
explicit ClassB( const int& num );

explicit operator ClassA() const throw();
explicit operator int() const throw();
explicit operator float() const throw();

int getNumber() const;

private:
int _number;
}

Display

Thus, compiler won't get confused in overload resolution with ambiguous calls that
static_cast<> has to choose from due to implicit conversions.

c++: cast operator vs. assign operator vs. conversion constructor priority

The statement t1 = t2; is equivalent to:

t1.operator=(t2);

Now the usual rules of overload resolution apply. If there's a direct match, that's the chosen one. If not, then implicit conversions are considered for use with the (automatically generated, "implicitly defined") copy-assignment operator.

There are two possible implicit, user-defined conversions. All user-defined conversions count equal, and if both are defined, the overload is ambiguous:

  • Convert t2 to a Test1 via the Test1::Test1(Test2 const &) conversion constructor.

  • Convert t2 to a Test1 via the Test2::operator Test1() const cast operator.

Conversion function / operator, static parse function or conversion constructor?

It's been a while since I posted this question and since then I've developed a few guidelines which I try to stick by in my projects, and I thought I'd share them so others who stumble upon this question can benefit from them as well.

I also welcome any input on the drawbacks of these guidelines, as they are probably not perfect.


  1. An object should only provide implicit conversion constructors for objects that are directly related to that class in some way. For example, a string class that offers a constructor that accepts a char array. When the conversion is lossy, or the classes are unrelated, use an explicit conversion constructor.

  2. An object should only provide implicit conversion operators to objects it is directly related to and for conversions which are lossless (no information is lost). Again, a string can provide a conversion to a character array. If this is not the case, an explicit conversion operator should be used.

  3. Provide toX() functions for all conversion operators (implicit and explicit). This isn't really a strict guideline, but more personal preference. Sometimes it is better to write toX() in your code as it conveys your intent better, in my opinion.

  4. Only use parse(X) functions if the function has to be static for some reason.

  5. Free functions can be used as an alternative to a parse(X) function, this depends on personal preference.


As I said, I welcome any input and I hope this may help someone.

Conversion operator vs deleted constructor

The question: is it the "right" behavior? I thought there is no particular precedence between conversion constructor and conversion operator [...]

That's not quite right. You're looking at the code as-is:

struct Y {
Y() {}
Y(X&) = delete;
};

But really there's something more there. To the compiler, Y looks like:

struct Y {
Y() {}
Y(X&) = delete;

Y(Y&&) = default;
Y(Y const&) = default;
};

The choice here isn't between Y(X&) and X::operator Y(). The choice is mainly between Y(X&) and Y(Y&&). And the former is a better match than the latter (regardless of, as you mention in the question, it's X& or X const& as the parameter). But it's deleted, so the conversion is ill-formed.


If we were copy-initializing instead of direct-initializing:

Y y = x;

Then both would be equally viable (and hence ambiguous). And yes, you really want that to be ambiguous. = delete does not remove from the candidate set!

Changing the constructor from Y(X&) to Y(X const&) would have the conversion function preferred.




Yes, one may call this silly, but indeed there are built-in types that behave just like that: substitute X <- int&, Y <- int&&

Yes, but in the original example, X and Y are different types. Here, they represent different value categories of the same type. The reason for this new example working or not working are entirely different:

X x;
Y y1(x); // Error
Y y2 = static_cast<Y>(x); // OK

is really:

int& x = ...;
int&& y(x); // error, can't bind rvalue reference to lvalue
int&& y2 = static_cast<int&&>(x); // ok. this is exactly std::move(x)

Reference binding to reference-compatible types isn't the same kind of question as conversion precedence.

Constructor is always used instead of explicit conversion operator

For static_cast<Foo<Y>>(x);, you're trying to construct a Foo<Y> from x (which is a Foo<X>) directly, for such context the converting constructor is preferred to conversion function.

(emphasis mine)

If both conversion functions and converting constructors can be used
to perform some user-defined conversion, the conversion functions and
constructors are both considered by overload resolution in
copy-initialization and reference-initialization contexts, but only
the constructors are considered in direct-initialization contexts
.

struct To {
To() = default;
To(const struct From&) {} // converting constructor
};

struct From {
operator To() const {return To();} // conversion function
};

int main()
{
From f;
To t1(f); // direct-initialization: calls the constructor
// (note, if converting constructor is not available, implicit copy constructor
// will be selected, and conversion function will be called to prepare its argument)
To t2 = f; // copy-initialization: ambiguous
// (note, if conversion function is from a non-const type, e.g.
// From::operator To();, it will be selected instead of the ctor in this case)
To t3 = static_cast<To>(f); // direct-initialization: calls the constructor
const To& r = f; // reference-initialization: ambiguous
}

You can make the conversion constructor to be discarded from the overload set for this case by SFINAE; i.e. make it valid only when the implicit conversion of the underlying pointers is allowed.

template <typename T2, typename = std::enable_if_t<std::is_convertible<T2*, T1*>::value>>
Foo(const Foo<T2>& other) : obj(other.obj) {}

LIVE

(involving explicit)Precedence with operator and constructor conversion

A very good question.

First of all, the explicit thing doesn't mean "this has precedence if explicit conversion is required". It means "this thing can only be invoked explicitly". So it creates some situations where it cannot be invoked, not forces it to be invoked in other situations.

Another thing to consider is that static_cast is direct initialization, while passing an argument to a function is copy initialization. Among other things, copy initialization never uses explicit constructors. Another thing to note is that direct initialization requires use of constructors for classes (explicit or not). Although that doesn't mean that a conversion can't perform direct initialization: it can be used to convert the argument of a constructor and if the constructor is a compiler-generated copy one, then it would look like the conversion function performed direct initialization (whereas in fact it was performed by the copy constructor). Try declaring a copy constructor without defining it (the disable-copy technique) and you'll see that conversion function no longer works in the direct initialization context: it'll compile, but will cause a linking error.

With that in mind:

  1. Obvious.
  2. Direct initialization requires a constructor, so it is invoked.
  3. Obvious.
  4. The same as 2, really. Declaring the conversion function explicit only prevents it from being invoked implicitly, it doesn't force its use in explicit contexts.
  5. Obvious.
  6. Again, direct initialization requires a constructor and it allows use of explicit ones.
  7. Obvious.
  8. Another direct initialization case.


Related Topics



Leave a reply



Submit