I have the following class:
template <typename T1>
class Foo {
public:
Foo(T1* obj) : obj(obj) {}
template <typename T2>
Foo(const Foo<T2>& other) : obj(other.obj) {}
template <typename T2>
explicit operator Foo<T2>() {
return Foo<T2>(static_cast<T2*>(obj));
}
T1* obj;
};
The intention of the second constructor is that an implicit conversion from Foo<X>
to Foo<Y>
is allowed iff an implicit conversion from X*
to Y*
is allowed.
The conversion operator is there to allow an explicit conversion from Foo<X>
to Foo<Y>
using an explicit conversion from X*
to Y*
.
But I noticed that the conversion operator never gets used. The compiler always uses the second constructor even when I do an explicit cast. This causes an error if an implicit conversion of the underlying types is not possible.
The following code can be used to test the class above.
class X {};
class Y : public X {};
int main() {
Y obj;
Foo<Y> y(&obj);
Foo<X> x = y; // implicit cast works as expected.
// y = x; // implicit conversion fails (as expected).
// y = static_cast<Foo<Y>>(x); // conversion fails because constructor is
// called instead of conversion operator.
}
Is there a way to cause the compiler to use the conversion operator for explicit conversions?
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
Take a look at the following quote from the standard
Static cast [expr.static.cast/4]
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.
This basically says that a constructor is preferred over a conversion operator, and that is what happens.
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