Можно ли сделать так, чтобы `=` предпочитало присвоение из преобразования вместо (удаленного) копирования?

Я обнаружил несколько потоков, которые в значительной степени подразумевают, что это невозможно, но ни один из них не использует точно такую ​​же комбинацию операторов и условий, поэтому я хотел бы спросить более конкретно. Надеюсь, это означает, что это быстрый и простой ответ для кого-то... так или иначе!

Рассмотрим пример прокси-класса, созданный для управления значением в большем блоке хранилища - как в этом упрощенном, но репрезентативном примере:

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; 
}
Другие вопросы по тегам