Могут ли шаблоны использоваться для доступа к структурным переменным по имени?
Предположим, что у меня есть такая структура:
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
Возможно, это проблема дизайна: мне нужен токен отмены для типа переменной, а не один для переменной...
Ответы
Ответ 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. Это вопрос дизайна.