cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

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

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +2, -3 Автор идеи: Андрей Руссков

Убрать ограничение на изменяемость полей для копирующего оператора присваивания

Сейчас для классов с 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.

apolukhin commented 3 years ago

Андрей Руссков, 1 июня 2018, 7:07 поправка: "в случае доступности копирующих операторов присваивания"

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

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

Андрей Руссков, 1 июня 2018, 12:43 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, 11 июня 2018, 18:40 А если есть такая структура:

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;
};

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

Андрей Руссков, 13 июня 2018, 5:59 Pavel Verutin, этот случай не попадает под условия генерации неявного оператора присваивания

languagelawyer, 14 июня 2018, 1:43

Предлагаю: не удалять неявный оператор копирующего присваивания из-за наличия 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?

WPMGPRoSToTeMa, 20 июня 2018, 18:05 Я думаю такую структуру проще обернуть в std::variant с одним типом.