Можно ли сделать так, чтобы `=` предпочитало присвоение из преобразования вместо (удаленного) копирования?
Я обнаружил несколько потоков, которые в значительной степени подразумевают, что это невозможно, но ни один из них не использует точно такую же комбинацию операторов и условий, поэтому я хотел бы спросить более конкретно. Надеюсь, это означает, что это быстрый и простой ответ для кого-то... так или иначе!
Рассмотрим пример прокси-класса, созданный для управления значением в большем блоке хранилища - как в этом упрощенном, но репрезентативном примере:
class SomeProxyThing {
std::uint32_t storage;
public:
operator std::uint16_t() const
{
return storage & 0x0000FFFF;
}
SomeProxyThing &operator=(std::uint16_t const value)
{
storage &= 0xFFFF0000;
storage |= value;
}
};
Я хочу, чтобы все назначения работали через определенные пользователем operator
s. В этом случае пользователь должен иметь возможность только передавать или убирать "открытый" тип std::uint16_t
, Возможно, я использую различные типы прокси-классов и хочу, чтобы это применялось ко всем из них. В идеале, для любой комбинации типов, я мог бы просто напечатать someProxy = anotherProxy
и пусть компилятор сделает все остальное.
Но когда левая и правая части назначения имеют одинаковые типы или типы, связанные с наследованием, оператор назначения копирования по умолчанию - конечно, - вступает в противоречие с этой целью. Копирует весь storage
таким образом, заглушая вторую половину этого uint32_t
- вместо того, чтобы копировать только "выставленное" значение по желанию. И это правильно! Для большинства случаев. Но я хотел бы получить способ "присвоения по конверсии", даже если типы LHS и RHS совпадают. Чтобы избежать этого, я могу:
- переопределить оператор присваивания копии, чтобы выполнить "прокси" копию, используя пользовательский
operator
s - это то, чем я занимаюсь, но это выглядит довольно странно и, как и любой пользовательский конструктор / оператор присваивания, нарушает тривиально копируемый статусstruct
- который мне нужно сохранить. Это ещеmemcpy()
все равно вg++
, но я хочу определенного поведения. - или же
= delete
оператор копирования-присвоения (что мы можем теперь сделать для типов TC). Но назначения все еще пытаются использовать это и выдают ошибку компиляции - так какdelete
означает "прервать с ошибкой, если я выбрал перегрузку", а не "исключить меня из разрешения перегрузки". Чтобы обойти это, я должен явно указать компилятору использовать оператор преобразования и присвоить его результат:
SomeProxyThing a, b;
a = 42;
b = static_cast<std::uint16_t>(a);
// a.k.a.
b.operator=( a.operator std::uint16_t() );
Кажется, нет способа сказать компилятору " игнорировать любую ошибку, вызванную вашей предпочтительной перегрузкой, и выбрать следующую лучшую ". Есть? В более общем смысле, есть ли способ / взломать / ужасающий kludge, в такой ситуации, чтобы заставить компилятор автоматически использовать / предпочитать определенные operator
s?
Другими словами, в идеале, в
SomeProxyThing a, b;
a = 42;
b = a;
тот b = a;
действительно сделал бы это:
b = static_cast<std::uint16_t>(a);
// a.k.a.
b.operator=( a.operator std::uint16_t() );
без необходимости вводить это вручную, используйте static_cast
или реализовать именованные методы get/set. В идеале я хочу, чтобы операции чтения / записи для любого такого прокси-сервера выглядели точно так же, как операции чтения / записи для базовых типов в написанном коде, все с использованием =
,
Я сильно подозреваю, что это невозможно... но подтверждение было бы неплохо!
2 ответа
Вы можете сделать это:
#include <stdint.h>
#include <iostream>
#include <type_traits>
using namespace std;
class Proxy_state
{
protected:
uint32_t storage;
public:
// Access to the bytes
};
static_assert( is_trivially_copyable<Proxy_state>::value, "!" );
class Some_proxy_thing
: public Proxy_state
{
private:
public:
operator std::uint16_t() const
{
return storage & 0x0000FFFF;
}
auto operator=( uint16_t const value )
-> Some_proxy_thing&
{
clog << "=(uint16_t)" << endl;
storage &= 0xFFFF0000;
storage |= value;
return *this;
}
auto operator=( Some_proxy_thing const& value )
-> Some_proxy_thing&
{ return operator=( static_cast<uint16_t>( value ) ); }
};
static_assert( not is_trivially_copyable<Some_proxy_thing>::value, "!" );
auto main()
-> int
{
Some_proxy_thing a{};
Some_proxy_thing b{};
const Some_proxy_thing c = b;
a = c;
a = 123;
a = b;
}
Здесь выводятся все три назначения (в стандартный поток ошибок) =(uint16t)
,
Соответствие / несоответствие типов времени компиляции можно контролировать с помощью std::enable_if. Неявное преобразование типов можно отключить с помощью явного ключевого слова. Все конструкторы копирования и перемещения могут быть явно удалены, чтобы избежать копирования, а конструктор по умолчанию может быть явно помечен как стандартный. Редактировать: ранний ответ учитывает первую часть вопроса о том, что "пользователь должен иметь возможность только передать или получить" открытый "тип", поэтому все преобразования должны быть явными, однако для завершения ответа вы можете определить тривиально копируемое Класс и использовать это в вашем прокси-классе Может быть то, что вы хотели:
#include <cstdint>
#include <type_traits>
struct copyable{
std::uint32_t number = 0x0;
};
class SomeProxyThing {
public:
explicit operator std::uint16_t() const
{
return storage.number & 0x0000FFFF;
}
template <typename T, typename std::enable_if<std::is_same<T, std::uint16_t>::value, int>::type=0>
SomeProxyThing& operator=(T value)
{
storage.number &= 0xFFFF0000;
storage.number |= value;
return *this;
}
SomeProxyThing()=default;
SomeProxyThing(const SomeProxyThing&)=delete;
SomeProxyThing(SomeProxyThing&&)=delete;
SomeProxyThing& operator=(const SomeProxyThing& other) {
this->storage.number = static_cast<std::uint16_t>(other.storage.number);
}
SomeProxyThing& operator=(SomeProxyThing&& other) {
this->storage.number = static_cast<std::uint16_t>(other.storage.number);
}
private:
copyable storage;
};
int main()
{
SomeProxyThing a, b;
a = static_cast<std::uint16_t>(43);
b = a;
}