如何检索C99可变参数宏的最后一个参数?

Myr*_*ria 5 c c++ c99 c-preprocessor

Visual Studio的失败static_assert的错误消息完全由一个错误代码和static_assert的第二个参数组成,没有任何其他消息表明它是静态断言失败.我想制作一个宏来解决这个问题.例如,作为第一次尝试:

#define STATIC_ASSERT(x) static_assert(x, "static assertion failed: " #x)
Run Code Online (Sandbox Code Playgroud)

您遇到的第一个问题是C预处理器不理解< >为封闭分隔符,这会导致模板出现语法错误.以下内容变为非法:

template <typename T, typename U>
auto SafeMultiply(T x, U y) -> decltype(x * y)
{
    STATIC_ASSERT(std::is_same<T, U>::value);
    STATIC_ASSERT(!std::numeric_limits<T>::is_signed);
    if (x > (std::numeric_limits<decltype(x * y)>::max)())
        throw std::overflow_error("multiplication overflow");
    return x * y;
}
Run Code Online (Sandbox Code Playgroud)

这是非法的,因为第一个中的T和U之间的逗号STATIC_ASSERT被解释为分隔两个宏参数,而不是模板参数.C预处理器抛出错误,因为STATIC_ASSERT宏只接受一个参数.

这个问题的两个主要解决方案是使用双括号,最近使用可变参数宏:

// Invoke the macro this way...
STATIC_ASSERT((std::is_same<T, U>::value));
// ...or define it this way:
#define STATIC_ASSERT(...) static_assert((__VA_ARGS__), \
    "static assertion failed: " #__VA_ARGS__)
Run Code Online (Sandbox Code Playgroud)

后一种解决方案更好,只需要更改宏定义.(__VA_ARGS__新定义中的额外括号是为了在某些更奇怪的情况下保持正确的操作顺序.在这个特定的宏中可能无关紧要,但在宏定义中将括号括在宏的参数周围是个好习惯. )

现在,如果我想更改我的STATIC_ASSERT宏来获取标准C++之类的消息static_assert,但是在消息中添加前缀呢?有点像这样,但支持使用std::is_same<T, U>不使用双括号:

// Causes a syntax error :(
#define STATIC_ASSERT(expr, msg) static_assert((expr), \
    "static assertion failed: " msg)
STATIC_ASSERT(std::is_same<T, U>, "x and y are not of the same type");
Run Code Online (Sandbox Code Playgroud)

如果我可以获得可变参数宏的最后一个参数,它将工作:

// I wish this'd work
#define STATIC_ASSERT(..., msg) static_assert((__VA_ARGS__), \
    "static assertion failed: " msg)
STATIC_ASSERT(std::is_same<T, U>, "x and y are not of the same type");
Run Code Online (Sandbox Code Playgroud)

但由于这不合法,我如何合法地获取...宏参数集的最后一个参数?当然,我可以反转参数顺序,但那时它不一样static_assert.

Leu*_*nko 7

在完全一般的情况下,没有简单的方法来获取最后一个宏参数,但是您可以轻松实现一个版本,该版本可以获取参数列表的最后一个元素,达到某个预定的最大值。对于现实世界的代码来说,像 64 个参数这样的参数通常就足够了。

您需要做的就是计算传递的参数数量,然后N-1从列表中返回元素:

// count arguments
#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

// utility (concatenation)
#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define M_GET_ELEM(N, ...) M_CONC(M_GET_ELEM_, N)(__VA_ARGS__)
#define M_GET_ELEM_0(_0, ...) _0
#define M_GET_ELEM_1(_0, _1, ...) _1
#define M_GET_ELEM_2(_0, _1, _2, ...) _2
#define M_GET_ELEM_3(_0, _1, _2, _3, ...) _3
#define M_GET_ELEM_4(_0, _1, _2, _3, _4, ...) _4
#define M_GET_ELEM_5(_0, _1, _2, _3, _4, _5, ...) _5
#define M_GET_ELEM_6(_0, _1, _2, _3, _4, _5, _6, ...) _6
#define M_GET_ELEM_7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
#define M_GET_ELEM_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
#define M_GET_ELEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define M_GET_ELEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10

// Get last argument - placeholder decrements by one
#define M_GET_LAST(...) M_GET_ELEM(M_NARGS(__VA_ARGS__), _, __VA_ARGS__ ,,,,,,,,,,,)
Run Code Online (Sandbox Code Playgroud)

您可以通过几分钟的复制和粘贴将其扩展到您想要的有限数量。