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

How can I retrieve the last argument of a C99 variadic macro?

本文关键字:最后一个 参数 C99 何检索 检索      更新时间:2023-10-16

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

#define STATIC_ASSERT(x) static_assert(x, "static assertion failed: " #x)

您遇到的第一个问题是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;
}

这是非法的,因为在第一个STATIC_ASSERT中,T和U之间的逗号被解释为分隔两个宏参数,而不是模板参数。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__)

后一种解决方案更好,只需要更改宏定义。(在新的定义中,__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");

如果我能得到可变宏的最后一个参数,它就可以工作了:

// 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");

但是由于这是不合法的,我如何合法地获得...宏参数集的最后一个参数?当然,我可以反转参数的顺序,但这就和static_assert不一样了。

在一般情况下,获取最后一个宏参数没有简单的方法,但是您可以轻松实现一个版本,使参数列表的最后一个元素达到预定的最大值。对于实际代码,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__ ,,,,,,,,,,,)

您可以将此扩展到任意大的有限数量,只需几分钟的复制和粘贴。

我有个想法:

#define STATIC_ASSERT(message, ...) 
  static_assert((__VA_ARGS__), "static assertion failed: " message)
STATIC_ASSERT("x and y must have the same type", std::is_same<T, U>);