Нужна магия метапрограммирования, чтобы определить материнскую жилу битовых полей безошибочным способом


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

Файл, который мы будем называть 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 3

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), флаг (бит, соответствующий его номеру), строка сообщения и поместите в массив строки сообщения.