Я обнаружил несколько потоков, которые в значительной степени подразумевают, что это невозможно, но ни один из них не использует точно такую же комбинацию операторов и условий, поэтому я хотел бы спросить более конкретно. Надеюсь, это означает, что это быстрый и простой ответ для кого-то … так или иначе!
Рассмотрим пример прокси-класса, созданный для управления значением в большем блоке хранилища — как в этом упрощенном, но репрезентативном примере:
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()
s тем не мение в 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() );
Кажется, нет способа сказать компиляторуигнорируйте любую ошибку, вызванную вашей предпочтительной перегрузкой, и выберите следующую лучшую». Есть? В более общем смысле, есть ли способ / взломать / ужасающий клудж в такой ситуации, чтобы заставить компилятор автоматически использовать / предпочитать определенный 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. В идеале я хочу, чтобы операции чтения / записи для любого такого прокси-сервера выглядели точно так же, как операции чтения / записи для базовых типов в написанном коде, все с использованием =
,
Я сильно подозреваю, что это невозможно … но подтверждение было бы неплохо!
Вы можете сделать это:
#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;
}