Нужна магия метапрограммирования, чтобы определить материнскую жилу битовых полей безошибочным способом
Цель состоит в том, чтобы контролировать, какие типы пользователей могут выполнять какие операции на уровне пользовательского интерфейса. Этот код существует уже некоторое время; я просто хочу немного улучшить его. Файл, который я пытаюсь улучшить, вероятно, должен быть автоматически сгенерирован, но это было бы слишком большим изменением, поэтому я ищу более простое решение.
Файл, который мы будем называть PermissionBits.h
, имеет кучу таких:
// Here names are mangled; for example XYZ_OP_A is:
// permission to operation A in category/context XYZ
// SCU64 = static const unsigned __int64
// Some namespaces utilize all 64 bits
// The actual values (as long as they are proper bit fields)
// do not matter - they are always used by name
namespace XYZPermissionBits
{
SCU64 XYZ_OP_A = 1UI64 << 0; // 1 = 0x0000000000000001
SCU64 XYZ_OP_B = 1UI64 << 1; // 2 = 0x0000000000000002
SCU64 XYZ_OP_C = 1UI64 << 2; // 4 = 0x0000000000000004
SCU64 XYZ_OP_C = 1UI64 << 3; // 8 = 0x0000000000000008
SCU64 XYZ_OP_D = 1UI64 << 4; // 16 = 0x0000000000000010
SCU64 XYZ_OP_E = 1UI64 << 5; // 32 = 0x0000000000000020
SCU64 XYZ_OP_F = 1UI64 << 6; // 64 = 0x0000000000000040
SCU64 XYZ_OP_G = 1UI64 << 7; // 128 = 0x0000000000000080
SCU64 XYZ_OP_H = 1UI64 << 8; // 256 = 0x0000000000000100
SCU64 XYZ_OP_I = 1UI64 << 9; // 512 = 0x0000000000000200
SCU64 XYZ_OP_J = 1UI64 << 10; // 1024 = 0x0000000000000400
SCU64 XYZ_OP_K = 1UI64 << 11; // 2048 = 0x0000000000000800
SCU64 XYZ_OP_L = 1UI64 << 12; // 4096 = 0x0000000000001000
}
Даже с помощью ярлыка 1UI64 << <numBits>;
остаются проблемы, так как кодеры создают флаги с повторяющимися значениями, делать опечатки и т.д.
В идеале я хотел бы иметь макрос, который может быть красиво отформатирован и выглядеть следующим образом:
BITFIELDS_FOR_NAMESPACE(
//*************** <<== I want to make the namespace name more vivid
XYZPermissionBits,
//*************** <<== somehow if that is possible. It is not a must-have.
XYZ_OP_A, // Being able to add a comment here would be nice, but not critical
XYZ_OP_B,
XYZ_OP_C,
XYZ_OP_D,
XYZ_OP_E,
XYZ_OP_F,
XYZ_OP_G,
XYZ_OP_H,
XYZ_OP_I,
XYZ_OP_J,
XYZ_OP_K,
XYZ_OP_L
)
Я хотел бы, чтобы этот макрос был гибким и не позволял мне вводить менее 2 или более 65 аргументов - имя пространства имен + 64 флага. Возможно ли сделать то, что я хочу или близко к этому, или я должен прибегнуть к сгенерированному коду? Какие еще у вас есть советы?
5 ответов:
Тестовый пример с использованием Boost.Препроцессор:
#include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/comparison/greater.hpp> #include <boost/preprocessor/comparison/less.hpp> #include <boost/preprocessor/debug/assert.hpp> #include <boost/preprocessor/seq/size.hpp> #define CHECK_SIZE(size) \ BOOST_PP_ASSERT_MSG(BOOST_PP_GREATER(size, 1), "< 2 :(") \ BOOST_PP_ASSERT_MSG(BOOST_PP_LESS(size, 65), "> 64 :(") \ #define DO_MAKE_BITFIELDS(a, b, i, elem) \ SCU64 elem = 1UI64 << i; #define BITFIELDS_FOR_NAMESPACE(name, seq) \ CHECK_SIZE(BOOST_PP_SEQ_SIZE(seq)) \ namespace name { \ BOOST_PP_SEQ_FOR_EACH_I(DO_MAKE_BITFIELDS, _, seq) \ }
Использование:
BITFIELDS_FOR_NAMESPACE( XYZPermissionBits, (XYZ_OP_A) (XYZ_OP_B) // ... );
IFAIK, библиотека препроцессоров boost http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html имеет все примитивы, которые вам нужны.
Если вы все же решите пойти по пути генерации кода, то я предлагаю взглянуть на Cog.
Cog позволяет вставлять код python в виде комментариев в исходный файл C++ (или любого другого языка), а при запуске через Cog вывод python вставляется в качестве исходного кода, поэтому код генератора и сгенерированный вывод управляются в одном файле. Это упрощает техническое обслуживание. и кода на Python, как сгенерированный код был создан.
Вот ваш пример использования cog, включая сгенерированный код:
namespace XYZPermissionBits { /* [[[cog import cog operations = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"] assert 2 <= len(operations) <= 64 for idx,op in enumerate(operations): cog.outl("SCU64 XYZ_OP_%s = 1UI64 << %s;" % (op, idx)) ]]] */ SCU64 XYZ_OP_A = 1UI64 << 0; SCU64 XYZ_OP_B = 1UI64 << 1; SCU64 XYZ_OP_C = 1UI64 << 2; SCU64 XYZ_OP_D = 1UI64 << 3; SCU64 XYZ_OP_E = 1UI64 << 4; SCU64 XYZ_OP_F = 1UI64 << 5; SCU64 XYZ_OP_G = 1UI64 << 6; SCU64 XYZ_OP_H = 1UI64 << 7; SCU64 XYZ_OP_I = 1UI64 << 8; SCU64 XYZ_OP_J = 1UI64 << 9; SCU64 XYZ_OP_K = 1UI64 << 10; SCU64 XYZ_OP_L = 1UI64 << 11; // [[[end]]] }
Вместо макросов / препроцессоров / etc я бы использовал статический текстовый файл, похожий на оригинал. Кажется, что это гораздо легче понять и расширить или сохранить. Я мог бы определить каждое значение как сдвиг предыдущего значения, но это может показаться более запутанным для некоторых читателей. Чтобы предотвратить опечатки пользователей, я бы, вероятно, использовал более подробную аббревиатуру для каждого " op " вместо A, B, C (определяется дважды), D...
Я делал нечто подобное. Создайте макрос MAKE_THINGS, который выглядит следующим образом:
#define MAKE_THINGS \ MAKE_THING(NAME1) \ MAKE_THING(NAME2) \ MAKE_THING(NAME3) \ /* Include semantically-blank line after list */Затем можно определить MAKE_THING, начать объявление, вызвать MAKE_THINGS, завершить объявление и отменить определение MAKE_THING. При желании каждая вещь может включать в себя более одного атрибута:Например:
#define MAKE_THINGS \ MAKE_THING(HAPPY,"Happy") \ MAKE_THING(SAD_AND_BLUE,"Sad and blue") \ MAKE_THING(SLEEPY,"Sleepy") \ /* Include semantically-blank line after list */ #define MAKE_THING(x,y) NUMBER_##x, typedef enum {MAKE_THINGS LAST_THING} THING_NUMBER; #undef MAKE_THING #define MAKE_THING(x,y) FLAG_##x = (1L << NUMBER##x), typedef enum {MAKE_THINGS ALL_FLAGS = (1 << LAST_THING)-1} THING_FLAGS; #undef MAKE_THING #define MAKE_THING(x,y) const char *MSG_##x = y; MAKE_THINGS #undef MAKE_THING #define MAKE_THING(x,y) MSG_##x, const char *thing_names[] = {MAKE_THINGS 0}; #undef MAKE_THINGОбратите внимание, что все объявления автоматически хранятся параллельно. Каждая вещь получает число (0..N-1), флаг (бит, соответствующий его номеру), строка сообщения и поместите в массив строки сообщения.