Можно ли использовать шаблоны для доступа к структурным переменным по имени? - PullRequest
17 голосов
/ 23 марта 2009

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

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 мне нужно получить доступ к переменным по имени.

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

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

и тогда я могу позвонить в FUNC (a) или FUNC (b).

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

РЕДАКТИРОВАТЬ : Я попытаюсь объяснить, почему я не могу использовать указатели и мне нужен доступ к переменной по имени. В основном структура содержит состояние системы. Эта система должна "отменить" свое состояние по запросу. Отмена обрабатывается с использованием интерфейса 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

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

Ответы [ 9 ]

35 голосов
/ 23 марта 2009

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

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

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

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

Тогда вам нужно что-то вроде map, которое выполняет поиск во время компиляции. Давайте назовем это 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;

C ++ создаст тип для 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.

19 голосов
/ 23 марта 2009
#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;
}
8 голосов
/ 05 января 2014

В дополнение к ответу Даниэля Эрвикера мы можем использовать вариационные шаблоны в новом стандарте C ++ для достижения того же.

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;
6 голосов
/ 23 марта 2009

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

#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.

4 голосов
/ 23 марта 2009

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

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

3 голосов
/ 23 марта 2009

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

Вторым хитом в Google для «шаблонов отражения C ++» стала статья « Поддержка отражений с помощью шаблонного метапрограммирования ». Это должно начать вас. Даже если это не совсем то, что вы ищете, это может показать вам способ решения вашей проблемы.

2 голосов
/ 23 марта 2009

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

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

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

EDIT

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

2 голосов
/ 23 марта 2009

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

1 голос
/ 23 марта 2009

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

Вы правы в разделе РЕДАКТИРОВАНИЯ. Это это вопрос дизайна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...