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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With