Использование переопределения оператора= базового класса не компилируется

Вопрос или проблема

В этом примере кода, почему не работает
using IParameterBase<TYPE>::operator=;
и работает оператор присваивания базового класса?

Я недавно перешел на эту шаблонную версию, ранее я писал отдельные классы типов, где этот механизм работал.

#include <cstdint>
#include <cstddef>

    class IParameter
    {
    public:
        explicit IParameter(const size_t size) : size_{size} {};
        virtual ~IParameter() = default;

        virtual void copy(uint8_t*& addr) = 0;
    private:
        const size_t size_;
    };

    template <class TYPE>
    class IParameterBase : public IParameter
    {
    public:
        explicit IParameterBase(const TYPE value) : IParameter{sizeof(TYPE)}, value_{value} {};
        ~IParameterBase() = default;

        virtual void update(const TYPE value) = 0;

        operator auto() const {return get();};

        TYPE operator= (const TYPE value) { update(value); return get(); };

        TYPE get() const {return value_;};

        protected:
        TYPE value_;
    };

    template <class TYPE>
    class ParameterTx : public IParameterBase<TYPE>
    {
    public:
        explicit ParameterTx(const TYPE value) : IParameterBase<TYPE>{value} {};

        using IParameterBase<TYPE>::operator=;

        void copy(uint8_t*& addr) override
        {
            /* копировать данные */
        }
        void update(const TYPE value) override
        {
            this->value_ = value;
        }
    };

int main ()
{
    ParameterTx<uint16_t> param1{0};
    ParameterTx<uint16_t> param2{1};

    param1 = 16;
    param2 = 5;

    param1 = param2;
}

Код здесь:
https://godbolt.org/z/3vqd4ebYM

Я ожидаю, что присваивание внизу param1 = param2; будет разрешено в uint16_t, вместо этого он пытается скопировать объект, что мне не нужно.

Компилятор добавил эту дополнительную функцию.

template <class TYPE>
class IParameterBase : public IParameter
{
public:
...
  TYPE operator= (const TYPE value) { update(value); return get(); };

   // следующая
  IParameterBase<TYPE>& operator= (const IParameterBase<TYPE>& value) = delete;
...
};

Потому что базовый IParameter не может быть скопирован, так как содержит член const.

С кодом выше компилятор не может скомпилировать присваивание param1 = param2, потому что удаленный оператор присваивания является точным совпадением, в то время как другой требует неявного преобразования.

Решение состоит в том, чтобы отменить удаление оператора присваивания внутри IParameterBase

TYPE operator= (const IParameterBase<TYPE>& value) 
{ update(value.value_); return this->get(); };

демонстрация godbolt

Почему не работает using IParameterBase::operator=;, а оператор присваивания базового класса работает? Вместо этого он пытается скопировать объект.

Потому что, когда вы написали param1 = param2, вы передаете param2 как параметр оператора присваивания operator=, и param2 имеет тип ParameterTx<uint16_t>, так что только удаленный оператор копирования присваивания= является кандидатом, потому что оператор, предоставленный using IParameterBase<TYPE>::operator=;, имеет параметр типа const uint16_t value.

И поскольку оператор копирования присваивания удален, потому что базовый класс IParameter имеет член const, мы получаем упомянутую ошибку.

Чтобы решить эту проблему, вы можете либо явно привести тип перед передачей param2, либо разрешить наследованному operator= (через using) участвовать в разрешении перегрузки, отменив удаление из IParameterBase, как показано в другом ответе.

Ответ или решение

В вашем коде возникла проблема с перегрузкой оператора присваивания из-за наличия удалённого конструктора копирования в базовом классе IParameter. Давайте разберёмся, почему это происходит и как это можно исправить.

Когда вы написали строку param1 = param2;, компилятор ищет подходящий оператор присваивания, который может принять объект типа ParameterTx<uint16_t>. В вашем случае есть следующие перегрузки:

  1. TYPE operator= (const TYPE value) – принимает значение типа const TYPE.
  2. Удалённый оператор: IParameterBase<TYPE>& operator= (const IParameterBase<TYPE>& value) – принимает ссылку на IParameterBase.

Так как у вас в базовом классе IParameter есть член с const (размер параметра), оператор присваивания копии в IParameterBase был автоматически удалён компилятором, из-за чего при попытке присваивания param1 = param2; компилятор выбирает удалённый оператор, который не может быть вызван.

Вы используете конструкцию using IParameterBase<TYPE>::operator=;, чтобы сделать доступным оператор присваивания из базового класса, однако, поскольку удалённый оператор является более подходящим кандидатом для перегрузки, компилятор выбирает его, что в итоге приводит к ошибке.

Решение проблемы состоит в том, чтобы явно определить оператор присваивания для вашего класса IParameterBase, который будет принимать другой объект типа IParameterBase. Вот как вы можете сделать это:

template <class TYPE>
class IParameterBase : public IParameter
{
public:
    explicit IParameterBase(const TYPE value) : IParameter{sizeof(TYPE)}, value_{value} {};
    ~IParameterBase() = default;

    virtual void update(const TYPE value) = 0;

    operator auto() const { return get(); };

    TYPE operator= (const TYPE value) { 
        update(value); 
        return get(); 
    };

    // Явно объявляем оператор присваивания
    IParameterBase<TYPE>& operator= (const IParameterBase<TYPE>& value) {
        if (this != &value) { // Избегаем присваивания самому себе
            update(value.value_);
        }
        return *this; // Возвращаем *this для поддержки цепочки присваиваний
    }

    TYPE get() const { return value_; }

protected:
    TYPE value_;
};

Теперь, с этим изменением, когда вы пишете param1 = param2;, компилятор найдёт подходящий оператор присваивания и будет использовать только что определённую перегрузку для передачи значения из param2 в param1.

Таким образом, проблема решена, и ваше выражение будет работать как ожидается. Вы также можете удалить строку с using IParameterBase<TYPE>::operator=;, так как теперь добавленный оператор решает ваш вопрос.

Важно помнить, что всегда нужно следить за доступностью вашей перегрузки оператора при наличии удалённых вариантов; это поможет избежать подобных ошибок в будущем.

Оцените материал
Добавить комментарий

Капча загружается...