Подтвердить что ты не робот

Могут ли шаблоны использоваться для доступа к структурным переменным по имени?

Предположим, что у меня есть такая структура:

struct my_struct
{
  int a;
  int b; 
}

У меня есть функция, которая должна установить новое значение для "a" или "b". Эта функция также требует указать, какую переменную установить. Типичным примером может быть следующее:

void f(int which, my_struct* s, int new_value)
{
  if(which == 0)
     s->a = new_value;
  else
     s->b = new_value; 
}

По причинам, которые я не буду писать здесь, я не могу передать указатель на a/b на f. Поэтому я не могу вызвать f с адресом my_struct:: a или my_struct:: b. Еще одна вещь, которую я не могу сделать, - объявить вектор (int vars [2]) в my_struct и передать целое число как индекс в f. В основном в f мне нужно получить доступ к переменным по имени.

Проблема с предыдущим примером заключается в том, что в будущем я планирую добавить больше переменных в структуру, и в этом случае я буду помнить о добавлении дополнительных операторов if в f, что плохо для переносимости. То, что я могу сделать, это написать f как макрос, например:

#define FUNC(which)
void f(my_struct* s, int new_value) \
{ \
        s->which = new_value; \
} 

а затем я могу вызвать FUNC (a) или FUNC (b).

Это будет работать, но я не люблю использовать макросы. Поэтому мой вопрос: есть ли способ достичь той же цели, используя шаблоны вместо макросов?

EDIT. Я попытаюсь объяснить, почему я не могу использовать указатели, и мне нужен доступ к переменной по имени. В основном структура содержит состояние системы. Эти системы должны "отменить" свое состояние по запросу. Undo обрабатывается с помощью интерфейса undo_token следующим образом:

class undo_token
{
public:
   void undo(my_struct* s) = 0;
};

Поэтому я не могу передавать указатели на метод отмены из-за полиморфизма (mystruct также содержит переменные других типов).

Когда я добавляю новую переменную в структуру, я обычно добавляю новый класс, например:

class undo_a : public undo_token
{
  int new_value;
public:
  undo_a(int new_value) { this->new_value = new_value; }
  void undo(my_struct *s) { s->a = new_value}
};

Проблема в том, что я не знаю указателя на s при создании токена, поэтому я не могу сохранить указатель на s:: a в конструкторе (который бы решил проблему). Класс для "b" тот же, просто мне нужно написать "s- > b" вместо s- > a

Возможно, это проблема дизайна: мне нужен токен отмены для типа переменной, а не один для переменной...

4b9b3361

Ответ 1

#include <iostream>
#include <ostream>
#include <string>

struct my_struct
{
    int a;
    std::string b;
};

template <typename TObject, typename TMember, typename TValue>
void set( TObject* object, TMember member, TValue value )
{
    ( *object ).*member = value;
}

class undo_token {};

template <class TValue>
class undo_member : public undo_token
{
    TValue new_value_;
    typedef TValue my_struct::* TMember;
    TMember member_;

public:
    undo_member(TMember member, TValue new_value):
        new_value_( new_value ),
        member_( member )
    {}

    void undo(my_struct *s) 
    { 
        set( s, member_, new_value_ );
    }
};    

int main()
{
    my_struct s;

    set( &s, &my_struct::a, 2 );
    set( &s, &my_struct::b, "hello" );

    std::cout << "s.a = " << s.a << std::endl;
    std::cout << "s.b = " << s.b << std::endl;

    undo_member<int> um1( &my_struct::a, 4 );
    um1.undo( &s );

    std::cout << "s.a = " << s.a << std::endl;

    undo_member<std::string> um2( &my_struct::b, "goodbye" );
    um2.undo( &s );

    std::cout << "s.b = " << s.b << std::endl;

    return 0;
}

Ответ 2

Чтобы ответить на точный вопрос, есть, но это довольно сложно, и это будет чисто компиляцией. (Если вам нужен просмотр во время выполнения, используйте указатель-к-члену - и на основе вашего обновленного вопроса вы, возможно, неправильно поняли, как они работают.)

Во-первых, вам нужно что-то, что вы можете использовать для представления "имени члена" во время компиляции. В метапрограммировании времени компиляции все, кроме целых чисел, должно быть представлено типами. Таким образом, вы будете использовать тип для представления члена.

Например, член целочисленного типа, который хранит возраст человека, а другой для хранения их фамилии:

struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };

Тогда вам нужно что-то вроде map, которое выполняет поиск во время компиляции. Пусть называется it ctmap. Пусть он поддерживает до 8 участников. Во-первых, нам нужен заполнитель для представления отсутствия поля:

struct none { struct value_type {}; };

Затем мы можем переслать-объявить форму ctmap:

template <
    class T0 = none, class T1 = none,
    class T2 = none, class T3 = none,
    class T4 = none, class T5 = none,
    class T6 = none, class T7 = none
    >
struct ctmap;

Затем мы специализируем это для случая, когда полей нет:

template <>
struct ctmap<
    none, none, none, none,
    none, none, none, none
    >
{
    void operator[](const int &) {};
};

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

template <
    class T0, class T1, class T2, class T3,
    class T4, class T5, class T6, class T7
    >
    struct ctmap : public ctmap<T1, T2, T3, T4, T5, T6, T7, none>
    {
        typedef ctmap<T1, T2, T3, T4, T5, T6, T7, none> base_type;

        using base_type::operator[];
        typename T0::value_type storage;

        typename T0::value_type &operator[](const T0 &c)
        { return storage; }
};

Что, черт возьми, здесь происходит? Если вы положили:

ctmap<last_name, age> person;

С++ построит тип для person путем рекурсивного расширения шаблонов, потому что ctmap наследует от себя, и мы предоставляем хранилище для первого поля, а затем отбрасываем его, когда мы наследуем. Все это внезапно прекращается, когда полей больше нет, потому что специализация для всех none срабатывает.

Итак, мы можем сказать:

person[last_name()] = "Smith";
person[age()] = 104;

Это похоже на поиск map, но во время компиляции с использованием класса именования полей в качестве ключа.

Это означает, что мы также можем сделать это:

template <class TMember>
void print_member(ctmap<last_name, age> &person)
{
    std::cout << person[TMember()] << std::endl;
}

Это функция, которая печатает одно значение члена, где подлежащий печати элемент является параметром типа. Поэтому мы можем назвать это следующим образом:

print_member<age>(person);

Итак, да, вы можете написать вещь, которая немного похожа на struct, немного похожую на время компиляции map.

Ответ 3

В дополнение к Daniel Earwicker answer, мы можем использовать вариационные шаблоны в новом стандарте С++ для достижения того же.

template <typename T>
struct Field {
  typename T::value_type storage;

  typename T::value_type &operator[](const T &c) {
    return storage;
  }
};

template<typename... Fields>
struct ctmap : public Field<Fields>... {
};

Этот код чище и не имеет фиксированной привязки членов. Вы можете использовать его таким же образом

struct age { typedef int value_type; };
struct last_name { typedef std::string value_type; };

ctmap<last_name, age> person;

person[last_name()] = "Smith";
person[age()] = 104;

Ответ 4

Ответ Николая Голубева хорош, но его можно немного улучшить, используя тот факт, что указатели на элементы могут использоваться как параметры непигового шаблона:

#include <iostream>
#include <ostream>
#include <string>

struct my_struct
{
    int a;
    std::string b;
};

template <typename TObject, typename TMember, typename TValue>
void set( TObject* object, TMember member, TValue value )
{
    ( *object ).*member = value;
}

class undo_token {};

template <class TValue, TValue my_struct::* Member>
class undo_member : public undo_token
{
        // No longer need to store the pointer-to-member
        TValue new_value_;

public:
        undo_member(TValue new_value):
                new_value_(new_value)
        {}

        void undo(my_struct *s) 
        { 
                set( s, Member, new_value_ );
        }
};    

int main()
{
    my_struct s;

    set( &s, &my_struct::a, 2 );
    set( &s, &my_struct::b, "hello" );

    std::cout << "s.a = " << s.a << std::endl;
    std::cout << "s.b = " << s.b << std::endl;

    undo_member<int, &my_struct::a> um1( 4 );
    um1.undo( &s );

    std::cout << "s.a = " << s.a << std::endl;

    undo_member<std::string, &my_struct::b> um2( "goodbye" );
    um2.undo( &s );

    std::cout << "s.b = " << s.b << std::endl;

    return 0;
}

Это сокращает стоимость указателя на член из каждого экземпляра undo_member.

Ответ 5

Я не уверен, почему вы не можете использовать указатель, поэтому я не знаю, подходит ли это, но посмотрите С++: указатель на элемент данных класса, в котором описывается способ передачи указателя на элемент данных структуры/класса, который не указывает непосредственно на член, , но позже привязан к указателю struct/class. (выделение после редактирования плаката, объясняющее, почему указатель не может быть использован)

Таким образом, вы не передаете указатель на элемент - вместо этого он скорее похож на смещение внутри объекта.

Ответ 6

Похоже, что то, что вы ищете, называется " reflection", и да, он часто реализуется с некоторой комбинацией шаблонов и макросы. Будьте предупреждены, что решения по отражению часто являются беспорядочными и раздражающими для работы, поэтому вы можете сделать некоторые исследования в них, прежде чем погрузиться в код, чтобы узнать, действительно ли это то, что вы хотите.

Второе попадание в Google для "шаблонов отражения С++" было опубликовано на " Поддержка отражения посредством метапрограммирования шаблонов". Это должно заставить вас начать. Даже если это не совсем то, что вы ищете, это может показать вам способ решить вашу проблему.

Ответ 7

Вы не можете использовать шаблоны для решения этой проблемы, но зачем использовать структуру в первую очередь? Это похоже на идеальное использование для std:: map, которая будет сопоставлять имена со значениями.

Ответ 8

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

Если бы вы это сделали, я бы предложил вам использовать Boost.Fusion, чтобы описать вашу структуру с помощью полей с шаблонами. См. ассоциативные кортежи для получения дополнительной информации об этом. Оба типа структур могут быть фактически совместимы (одна и та же организация в памяти), но я уверен, что нет никакой возможности получить такую ​​гарантию от стандарта.

Если вы этого не сделаете, вы можете создать дополнение к структуре, которая даст вам доступ к полям так же, как это делают ассоциативные кортежи. Но это может быть немного словесным.

ИЗМЕНИТЬ

Теперь довольно ясно, что вы можете определить структуры так, как хотите. Поэтому я определенно предлагаю вам использовать boost.fusion.

Ответ 9

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

Вы правы в своем разделе EDIT. Это вопрос дизайна.