The following is a C++ snippet that demonstrates a class Foo
that wraps a std::optional<double>
, providing conversion operators for both double
and std::optional<double>
.
#include <iostream>
#include <optional>
class Foo {
public:
operator double() const {
std::cout << "Foo::operator double()" << std::endl;
return {};
}
operator std::optional<double>() const {
std::cout << "Foo::operator std::optional<double>()" << std::endl;
return {};
}
};
int main() {
Foo foo;
std::optional<double> value;
// Outputs "Foo::operator double()"
value = foo;
}
Surprisingly to me, when assigning an instance of Foo
to a std::optional<double>
, the conversion operator for double
is called instead of the conversion operator for std::optional<double>
.
When I replace std::optional
with my own custom Optional
class, as you can see below, the compiler selects the other conversion operator.
#include <iostream>
template<class T>
class Optional {
public:
T val;
Optional() = default;
Optional(T) {
std::cout << "Optional(T)" << std::endl;
}
Optional(Optional&&) noexcept {
std::cout << "Optional(Optional&&)" << std::endl;
}
Optional(const Optional<T>&) {
std::cout << "Optional(const Optional&)" << std::endl;
}
Optional& operator=(Optional&&) noexcept {
std::cout << "Optional::operator=(Optional&&)" << std::endl;
return *this;
}
operator const T& () const {
std::cout << "Optional::operator const T&()" << std::endl;
return val;
}
};
class Foo {
public:
operator double() const {
std::cout << "Foo::operator double()" << std::endl;
return {};
}
operator Optional<double>() const {
std::cout << "Foo::operator Optional<double>()" << std::endl;
return {};
}
};
int main() {
Foo foo;
Optional<double> value;
// Outputs
// Foo::operator Optional<double>()
// Optional::operator=(Optional&&)
value = foo;
}
Why does the compiler select a different conversion operator in each scenario?
Best Answer
Here is the cppreference list of assignment operator overloads. The only direct match is number 4:
It only participates in overload if the following requirements satisfied:
All requirements are satisfied with U being Foo. After that overload is chosen only
Foo::operator double() const
can assign to double value inoptional<double>