Ещё немного метапрограммирования
От: CEMb  
Дата: 22.12.22 11:23
Оценка:
Добрый день.

Есть ли какая-то возможность сделать так(метакод):


// header
class A
{
public:
    void Copy(const A& a);
private:
    // meta-code: want to copy this in Copy method
    int iVal_{};
    // just ptr, do nothing
    float* fPtr_{};
};


// body

void A::Copy(const A& a)
{
    iVal_ = a.iVal_; // i got this code, ok!
    //fPtr_ = a.fPtr_; - no code, i don't ask for it
}


Пояснения: у меня что-то типа базы данных. Есть класс, который читает объекты из этой условной базы, это такой мета-класс для описаний абстрактных объектов. Там очень много параметров, и что важно, они постоянно там добавляются-удаляются, в зависимости от требований проекта.
А есть классы, объекты которых инициализируются по описанию из этого мета-класса, и тут все эти классы делают выборку из параметров, типа:
select erw, dgf, oer, osd from metaclass where id = id

Грубо говоря, они по id-у находят объект в стоке и просто копируют нужные для себя поля.
И эти прикладные классы (их переменные) ещё активнее создаются и модифицируются, в зависимости от задач.
И у них есть тот самый метод Copy, который копирует из объекта-описания нужные данные.

Собственно, вопрос в том, что не хочется каждый раз, когда добавляется какой-то элемент в класс, идти писать его копирование. Это, конечно, дисциплинирует, но было пару раз, когда я забывал это сделать.
Можно ли что-то сделать, чтобы при описании переменной в классе указать, чтобы нагенерился код в копире? Один способ с помощью адовой шаблонной магии я знаю, но вот менять int val_ на что-то громоздкое не хочется, хочется иметь на руках именно переменную нужного типа и не лезть в неё геттерами-сеттерами, простота написания и доступа для меня критична.

Несколько лет назад такая ситуация была с конструкторами: добавил переменную, но забыл её обнулить. Я разбирал одно время старый код на работе и много такого видел. А теперь есть:
int val_{some};

которое решает эту проблему почти полностью. Я не успеваю смотреть все новые вещи в плюсах, может уже и тут что-то появилось? Даже было бы хорошо иметь assert на момент компиляции(java @NonNull вспомнилось), если я забыл положить переменную в ксерокс
Re: Ещё немного метапрограммирования
От: B0FEE664  
Дата: 22.12.22 14:49
Оценка: 8 (2)
Здравствуйте, CEMb, Вы писали:

CEM>Собственно, вопрос в том, что не хочется каждый раз, когда добавляется какой-то элемент в класс, идти писать его копирование.


// header
class A
{
public:
    void Copy(const A& a);
private:
  struct ToCopy
  {
    // meta-code: want to copy this in Copy method
    int iVal_{};
  } c;
  
  struct NoCopy
  {
    // just ptr, do nothing
    float* fPtr_{};
  } n;
};


// body
void A::Copy(const A& a)
{
    c = a.c;
}
И каждый день — без права на ошибку...
Re: Ещё немного метапрограммирования
От: rg45 СССР  
Дата: 22.12.22 14:53
Оценка: 3 (1) +1
Здравствуйте, CEMb, Вы писали:

CEM>Собственно, вопрос в том, что не хочется каждый раз, когда добавляется какой-то элемент в класс, идти писать его копирование. Это, конечно, дисциплинирует, но было пару раз, когда я забывал это сделать.

CEM>Можно ли что-то сделать, чтобы при описании переменной в классе указать, чтобы нагенерился код в копире? Один способ с помощью адовой шаблонной магии я знаю, но вот менять int val_ на что-то громоздкое не хочется, хочется иметь на руках именно переменную нужного типа и не лезть в неё геттерами-сеттерами, простота написания и доступа для меня критична.

По-моему, это несложно решается и без метапрограммирования. Просто все поля, которые должны копироваться(перемещаться), вносишь в состав безымянной структуры. Пишешь конструктор(ы) копирования(перемещения) и оператор(ы) присваивания, в которых манипулируешь структрурой в целом, а не каждым полем в отдельности. А те поля, которые не должны копироваться(перемещаться) оставляешь непосредственно в классе. После этого, при добавлении нового поля, остается лишь поместить его в правильное место — в класс, либо в структуру:

http://coliru.stacked-crooked.com/a/6bb55fd5f134eb65

class A
{
public:

    A() = default;
    
    A(const A& rhs) : props_(rhs.props_) {}
    A& operator = (const A& rhs) { props_ = rhs.props_; return *this; }
    
    void Copy(const A& a) { *this = a; }

private:
    struct
    {
        // meta-code: want to copy this in Copy method
        int iVal_{};
    } props_;
    // just ptr, do nothing
    float* fPtr_{};
};


P.S. И метод Copy здесь выглядит лишним, по-моему — вполне достаточно оператора присваивания.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 22.12.2022 14:56 rg45 . Предыдущая версия .
Re[2]: Ещё немного метапрограммирования
От: SomeOne_TT  
Дата: 22.12.22 15:02
Оценка:
Здравствуйте, rg45, Вы писали:

R> void Copy(const A& a) { *this = a; }


Давненько не писал на плюсах, похоже, забыл многое — как это присваивание работает?
Re: Ещё немного метапрограммирования
От: Sm0ke Россия ksi
Дата: 22.12.22 15:09
Оценка: 3 (1)
Я бы сгруппировал свойства для копирования в отдельную структуру без методов.
В своём классе заводишь одно свойство, которое будешь копировать. Остальные свойства будут отдельно.
Это называется инкапсулирование.
Потом в своих классах в методе Copy() или ещё где просто пишешь this->m_props = other.m_props;

class A {
  struct props { int a, b, c; };
  props m_props;
  omg wtf; // остальные свойства

  public:
  void Copy(const A & other) { m_props = other.m_props; }
};


Можно обобщить шаблонами, если таких классов много.

template <typename T>
struct with_copy {
  using target_pointer = T *;
  target_pointer target() { return static_cast<target_pointer>(this); }
  void Copy(const T & other) { target()->m_props = other.m_props; }
};

struct A : public with_copy<A> {
  struct props { int a, b, c; };
  props m_props;
  // прочие свойства
};

struct B : public with_copy<B> {
  struct props { double x, y; };
  props m_props;
  // прочие свойства
};


Вариант 2.
Свой класс наследуешь от структуры со свойствами для копирования. В Copy() делаешь static_cast на базу для вызова оператора присвоения.
В этом случае меньше точек при доступе к свойствам, в отличии от варианта с инкапсуляцией.

struct props_A { /* ... */ };
class A : public props_A {
  data some_data; // ну ты понял)

  props_A & props_only() { return *static_cast<props_A *>(this); }
  void Copy(const A & other) { props_only() = other.props_only(); }
};
Re[3]: Ещё немного метапрограммирования
От: rg45 СССР  
Дата: 22.12.22 15:11
Оценка:
Здравствуйте, SomeOne_TT, Вы писали:

R>> void Copy(const A& a) { *this = a; }


SO_>Давненько не писал на плюсах, похоже, забыл многое — как это присваивание работает?


*this — это lvalue выражение, доступное для присваивания. Работает так же, как если бы мы написали:

A& t = *this;
t = a;


P.S. Оператор присваивания здесь может быть как определенным пользователем, так и автоматически сгенерированным компилятором, это уже не суть.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 22.12.2022 15:13 rg45 . Предыдущая версия .
Re[4]: Ещё немного метапрограммирования
От: SomeOne_TT  
Дата: 22.12.22 16:33
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, SomeOne_TT, Вы писали:


R>>> void Copy(const A& a) { *this = a; }


R>P.S. Оператор присваивания здесь может быть как определенным пользователем, так и автоматически сгенерированным компилятором, это уже не суть.


А, т.е. обычный оператор работает, ну да, спасибо.
Re: Ещё немного метапрограммирования
От: CEMb  
Дата: 23.12.22 10:14
Оценка: -1
Всем большое спасибо

Но, получается, прям удобного решения нету.

Тут есть одна фундаментальная проблема для идеального решения: как правильно сделать описание полей? В той же яве это вроде можно через аннотации.
Вторая (моя) проблема для решения через структуру: у поля группируются по смыслу, это удобно для разработки, и совсем не хочется их делить по признаку copyable и выносить в отдельные структуры, к тому же к ним придётся для доступа писать ещё отдельный код (хотя с т.з. генерируемого кода разницы нет) — это третья проблемка.

Поиском нашёл ещё вот такое вот интересное решение, на основе препроцессора boost.PP.

Самое забавное, даже если у нас будет решение вида:
int iVal_; // do: place it in Copy()

это всё равно не спасает программиста от забывания добавить этот мета-код
К тому же есть ситуации, когда не весь "копируемый" контент надо копировать. А ещё есть директивы в описании, которые указывают копировать свои (или родительские) определённые части описания. Не сильно часто, но используется, и решение со структурами внутри класса тут не подходит.

Всем спасибо ещё раз, буду думать, как теперь с этим жить
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.