需要元编程魔法以无错误的方式定义位字段的母脉

发布于 2024-09-07 20:23:10 字数 2122 浏览 3 评论 0原文

目标是控制允许哪些类型的用户在 UI 级别执行哪些操作。该代码已经存在一段时间了;我只是想稍微改进一下。 我试图改进的文件可能应该是自动生成的,但这变化太大,所以我寻求一个更简单的解决方案。

我们将调用 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 << 的帮助下,; 快捷方式仍然存在问题,因为编码人员创建具有重复值的标志、输入错误等。

理想情况下,我想要一个可以很好地格式化并且看起来像这样的宏:

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 个标志。是否可以做我想要的或接近它的事情,或者我应该求助于生成的代码?您还有什么其他建议?

The goal is to control which types of users are allowed to perform which operations at the UI level. This code has been in place for a while; I just want to improve it a bit.
The file which I am trying to improve should probably be auto-generated, but that would be too big of a change, so I seek a simpler solution.

A file which we shall call PermissionBits.h has a bunch of these:

// 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 
}

Even with the help of 1UI64 << <numBits>; shortcut there are still problems, as coders create flags with duplicate values, make typos, etc.

Ideally I would like a macro which can be nicely formatted and look like:

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
)

I would like this macro be flexible and prevent me from entering less than 2 or more than 65 arguments - namespace name + 64 flags. Is it possible to do what I want or close to it, or should I resort to generated code? What other advice do you have?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

故乡的云 2024-09-14 20:23:10

使用 Boost.PreProcessor 测试示例:

#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)
    // ...
);

Tested example using Boost.PreProcessor:

#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) \
  }

Usage:

BITFIELDS_FOR_NAMESPACE(
    XYZPermissionBits,
    (XYZ_OP_A)
    (XYZ_OP_B)
    // ...
);
我很OK 2024-09-14 20:23:10

IFAIK,boost 预处理器库 http://www. boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html 拥有您需要的所有原语。

IFAIK, The boost preprocessor library http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html has all the primitives you need.

刘备忘录 2024-09-14 20:23:10

如果您确实决定采用代码生成路线,那么我建议您看看 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]]]
}

If you do decide to go the code generation route then I suggest taking a look at Cog.

Cog lets you embed python code as comments in the C++ (or any other language) source file, and when run through Cog the python output is inserted as source code, so the generator code and generated output are all managed in the same file. This keeps maintenance simple. and the python code documents how the generated code was created.

Here is your example using cog, including the generated code:

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]]]
}
难以启齿的温柔 2024-09-14 20:23:10

我会坚持使用与原始文件类似的静态文本文件,而不是宏/预处理器/等。它似乎更容易理解、扩展或维护。我可能将每个值定义为前一个值的偏移,但这对某些读者来说可能看起来更令人困惑。为了帮助防止用户输入错误,我可能会为每个“op”使用更详细的缩写,而不是 A、B、C(定义两次)、D...

Instead of macros/preprocessors/etc I'd stick with a static text file similar to the original. It seems much easier to understand and expand or maintain. I might define each value as a shift of the previous value, but that might seem more confusing to some readers. To help prevent typos by users, I'd probably use a more verbose abbreviation for each "op" instead of A, B, C (defined twice), D...

怪我入戏太深 2024-09-14 20:23:10

我曾经做过这样的事情。创建一个名为 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)、一个标志(与其数字相对应的位)、一个消息字符串以及消息字符串数组中的一个位置。

I've done something like this. Create a macro called MAKE_THINGS that looks like:

#define MAKE_THINGS \
  MAKE_THING(NAME1) \
  MAKE_THING(NAME2) \
  MAKE_THING(NAME3) \
  /* Include semantically-blank line after list */

One can then define MAKE_THING, start a declaration, call MAKE_THINGS, end the declaration, and undefine MAKE_THING. If desired, each thing can include more than one attribute:

For example:

#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

Note that all of the declarations are automatically kept parallel. Each thing gets a number (0..N-1), an flag (bit corresponding to its number), a message string, and a place in an array of message strings.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文