Допустим, я хочу определить классы следующей структуры:
struct MyClass {
int x;
bool y;
float z;
MyClass(QVariantMap data) : x(data["x"]), y(data["y"]), z(data["z"]) {}
};
Как видите, у меня есть QVariantMap (что-то похожее на std::map<std::string, boost::variant<...>>
для тех из вас, кто не знаком с Qt), из которых я хочу иметь возможность создавать такой тип без дополнительных знаний о его полях, отсюда и удобный конструктор, который должен «десериализовать» поля из карты.
Мне нужно несколько классов этого стиля, и я хочу, чтобы определения были максимально чистыми, чтобы обеспечить максимальную удобство сопровождения (в отношении опечаток в строковых ключах), удобочитаемость и автоматизацию.
Я думал о макроструктуре, подобной следующей:
DEF_CLASS(MyClass)(
DEF_FIELD(int, x)
DEF_FIELD(bool, y)
DEF_FIELD(float, z)
);
Я не вижу проблем, когда я хочу генерировать только поля, но не конструктор (возможен и другой способ, но я продемонстрирую только первый):
#define DEF_CLASS(CLASSNAME) \
struct CLASSNAME { \
_DEF_CLASS_TAIL/*..."curry the arguments"...*/
#define _DEF_CLASS_TAIL(FIELDS) \
FIELDS \
}
#define DEF_FIELD(TYPE, NAME) TYPE NAME;
Я определяю первый макрос, который запускает определение класса, и использую технику «карри» для пересылки вторых скобок во второй макрос, который помещает содержимое определения класса (FIELDS
) и закрывает его потом. Таким образом, такова была моя идея, у меня есть доступ к FIELDS
во втором макросе.
Но как теперь я могу вывести поля дважды, одно для определения фактических полей и одно для вывода инициализации члена?
Я знаю, что если я определю макрос DEF_CLASS_FIELD
двумя различными способами, а затем несколько «включить» поля из моего кода определения выше, по одному на каждое определение макроса, я мог бы правильно распечатать их одно за другим. Но так как список полей находится (и должен) быть в пределах определения класса, я не могу просто включить что-то дважды.
Есть ли другие варианты?
Я стараюсь избегать библиотеки препроцессора Boost, но если у вас есть хорошее решение, которое использует это, продолжайте. Однако я очень предпочитаю простое решение, если оно есть.
Вот пример, который на самом деле не создает структуру для кэширования значений из карты, а просто проверяет в конструкторе, что карта содержит поля, как обсуждалось в комментариях к открытому вопросу.
#define DEF_CLASS(CLASSNAME) \
struct CLASSNAME { \
CLASSNAME(QVariantMap& map) {\
_DEF_CLASS_TAIL/*..."curry the arguments"...*/
#define _DEF_CLASS_TAIL(FIELDS) \
FIELDS \
}};
#define CHK_FIELD(TYPE, NAME) \
if (typeid(TYPE)!=typeid(map[#NAME])) \
{ throw std::runtime_error(#NAME" missing or wrong type");}
DEF_CLASS(MyClass)(
CHK_FIELD(int, x)
CHK_FIELD(bool, y)
CHK_FIELD(float, z)
);
Это производит следующее из препроцессора (после запуска через astyle):
struct MyClass {
MyClass(QVariantMap& map) {
if (typeid(int) != typeid(map["x"])) {
throw std::runtime_error("x"" missing or wrong type");
}
if (typeid(bool) != typeid(map["y"])) {
throw std::runtime_error("y"" missing or wrong type");
}
if (typeid(float) != typeid(map["z"])) {
throw std::runtime_error("z"" missing or wrong type");
}
}
};
РЕДАКТИРОВАТЬ: Я не на 100% уверен, что сравнения typeid будет работать так, но это должно быть прямо заменить его проверкой, которая работает.
Других решений пока нет …