Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Visual Studio's error message for a failed static_assert consists entirely of just an error code and the second parameter to the static_assert, without any additional message indicating that it was a static assertion failure. I'd like to make a macro to solve this problem. For example, as a first try:

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

The first issue that you run into is that the C preprocessor doesn't understand < > as being enclosing delimiters, which results in syntax errors with templates. The following becomes illegal:

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

This is illegal because the comma between T and U in the first STATIC_ASSERT is interpreted as separating two macro parameters, rather than template parameters. The C preprocessor throws an error because the STATIC_ASSERT macro only takes one parameter.

The two main solutions to this problem are to use double parentheses and, more recently, to use variadic macros:

// 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__)

The latter solution is better, requiring only changes to the macro definition. (The extra parentheses around __VA_ARGS__ in the new definition are to preserve proper order of operations in some of the weirder cases. It may not matter in this particular macro, but it's a good habit to put parentheses around your macros' parameters in your macro definitions.)

Now what if I wanted to change my STATIC_ASSERT macro to take a message like the standard C++ static_assert, but adding a prefix to the message? Kind of like this, but supporting the use of std::is_same<T, U> without the use of double parentheses:

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

If I could get the last parameter of a variadic macro, it would work:

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

But since that's not legal, how can I legally get the last parameter of a ... macro parameter set? Sure, I could reverse the parameter order, but then it's not the same as static_assert.

like image 225
Myria Avatar asked Jun 03 '14 07:06

Myria


1 Answers

There's no easy way to get the last macro argument in the fully general case, but you can easily implement a version that gets the last element for argument lists up to some predetermined maximum. Something like 64 arguments is usually enough for real-world code.

All you need to do is count the number of arguments passed, and then return element N-1 from the list:

// 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__ ,,,,,,,,,,,)

You can extend this to as large a finite amount as you want with a few moments' copy and paste.

like image 170
Leushenko Avatar answered Sep 29 '22 05:09

Leushenko