Неявный оператор присваивания для классов с const полями

Андрей Руссков
Андрей Руссков

Сейчас для классов с const полями неявный оператор присваивания не генерируется, т.к. он эквивалентен поэлементному присваиванию полей, недоступному в случае const. Пример:

struct S {
  int a;
  const int b;
};

S s {1,2};
s = {3,4}; // error: non-static const member 'const int S::b', can't use default assignment operator @ gcc

Однако, при таком подходе теряется сама суть ограничения const для поля класса - неизменяемость поля выливается в незаменяемость экземпляра. В таком случае легально заменить экземпляр можно используя пару из деструктора и copy-конструктора:

auto p = &s;
s.~S();
new (std::launder(p)) S({3,4});

что куда хуже отражает суть намерений программиста.

 

Предлагаю: не удалять неявный оператор копирующего присваивания из-за наличия const полей в случае доступности копирующих конструкторов этих полей.

 

Влияние на существующий код: может поменяться поведение для кода, зависящего от свойства is_copy_assignable.

-1
рейтинг
7 комментариев
Андрей Руссков

поправка: "в случае доступности копирующих операторов присваивания"

Андрей Руссков
languagelawyer

Предлагается в неявном операторе присваивания делать placement new с конструктором копирования поверх константного члена? После такого этим объектом, или по крайней мере константным подобъектом, нельзя будет пользоваться без std::launder. И std::launder нужен не в том месте, как написано у автора предложения.

Похоже, предложение родилось из неправильного понимания std::launder.

languagelawyer
Андрей Руссков

languagelawyer, launder тут ни при чем, ровно как и placement new. Предложение призвано упростить работу со структурами/классами с const members. Например, есть у нас простая запись о сотруднике:

struct Record {
const int id {0};
const Date dateOfBirth {};
string name;
Position position;
// ...
};

предполагается, что в процессе работы приложения у сотрудника могут измениться имя (вдруг поменяет) и должность, но не могут измениться дата рождения и id записи. Однако при таком подходе одну запись нельзя заменять другой, что сильно ограничивает возможность их применения:

vector<Record> employees;
employees.push_back({id, date, name, position, ...}); // Легально
sort(employees.begin(), employees.end(), dateLess); // Ошибка
employees.erase(it); // Ошибка
partition(employees.begin(), employees.end(), [](auto &r) { return r.sex == Male; }); // Ошибка
Андрей Руссков
Pavel Verutin

А если есть такая структура:

struct TestStruct
{
  TestStruct(int someInt = 0) 
    : m_someInt(someInt), m_pInt(&m_someInt) {}
  TestStruct(TestStruct const & other)
    : m_someInt(other.m_someInt), m_pInt(&m_someInt) {}

private:
  int m_someInt;
  int * const m_pInt;
};

что должно произойти с указателем при вызове оператора присваивания?

Pavel Verutin
Андрей Руссков

Pavel Verutin, этот случай не попадает под условия генерации неявного оператора присваивания

Андрей Руссков
languagelawyer

> Предлагаю: не удалять неявный оператор копирующего присваивания из-за наличия const полей в случае доступности копирующих операторов присваивания этих полей.

В случае константных полей-классов неявный оператор и так не удаляется, если копирующий оператор для поля можно вызвать: https://wandbox.org/permlink/J5qKwNxMPEYwwF8F
Правда, он для этого должен быть с const-квалификатором, что довольно бессмысленно.

Если есть в классе есть "a non-static data member of const non-class type", то тогда да, неявный оператор определяется удалённым (http://eel.is/c++draft/class.copy.assign#7.2).  Только о какой "доступности" копирующего оператора присваивания может идти речь, например, для `const int`?

languagelawyer
WPMGPRoSToTeMa

Я думаю такую структуру проще обернуть в std::variant с одним типом.

WPMGPRoSToTeMa
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения