Ниже приведен фрагмент кода C++, демонстрирующий класс Foo
, обертывающий std::optional<double>
, предоставляющий операторы преобразования как для double
, так и для 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;
}
Для меня было неожиданностью, что при присвоении экземпляра Foo
объекту std::optional<double>
вместо оператора преобразования double
вызывается оператор преобразования для std::optional<double>
.
Когда я заменяю std::optional
своим собственным классом Optional
, как вы можете видеть ниже, компилятор выбирает другой оператор преобразования.
#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;
}
Почему компилятор выбирает разные операторы преобразования в каждом сценарии?
Примечание: ничто в этом коде не требует дополнительных вещей, которые делает std::endl
. Используйте ’’\n’` для завершения строки, если у вас нет веской причины не делать этого.
у вас есть несколько перегрузок оператора присваивания, template< class U = T > optional& operator=( U&& value );
соответствует прямому совпадению, а U — Foo.
@Джин, ты уверен в этом? Существует множество ограничений на то, когда различным шаблонным операторам присваивания разрешено участвовать в разрешении перегрузки.
@Реми Лебо - насколько я вижу, все устраивает
Вот список перегрузок оператора присваивания cppreference. Единственное прямое совпадение — номер 4:
template< class U = T >
optional& operator=( U&& value );
Он участвует в перегрузке только в том случае, если выполняются следующие требования:
- Совершенно перенаправленное задание: в зависимости от того, содержит ли *this значение перед вызовом, содержащееся значение либо инициализируется напрямую из std::forward(value) или назначается из станд:: вперед (значение). Функция не участвует в перегрузке разрешение, если только std::decay_t(until С++20) std::remove_cvref_t(поскольку C++20) не является std::optional, std::is_constructible_v<T, U> — правда, std::is_assignable_v<T&, U> — это истинно, и верно хотя бы одно из следующих условий: T не является скаляром тип; std::decay_t не является T.
Все требования удовлетворены, если U — Foo. После того, как эта перегрузка выбрана, только Foo::operator double() const
можно присвоить двойное значение в optional<double>
Компилятор решает, какую перегрузку он будет использовать, прежде чем рассматривать тип, которому вы пытаетесь его присвоить.