Вопрос или проблема
В этом примере кода, почему не работает
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(); };
Почему не работает
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>
. В вашем случае есть следующие перегрузки:
TYPE operator= (const TYPE value)
– принимает значение типаconst TYPE
.- Удалённый оператор:
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=;
, так как теперь добавленный оператор решает ваш вопрос.
Важно помнить, что всегда нужно следить за доступностью вашей перегрузки оператора при наличии удалённых вариантов; это поможет избежать подобных ошибок в будущем.