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

时间:2014-06-03 07:50:25

标签: c++ c c99 c-preprocessor

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不一样。

2 个答案:

答案 0 :(得分:4)

在完全一般的情况下,没有简单的方法可以获得最后一个宏参数,但是您可以轻松地实现一个版本,该版本可以获得参数列表的最后一个元素,达到某个预定的最大值。像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__ ,,,,,,,,,,,)

您可以根据需要将其扩展到您想要的大量有限数量的时间。复制并粘贴。

答案 1 :(得分:1)

这是一个想法:

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