Как я могу получить последний аргумент макроса C99 variadic?

Сообщение об ошибке 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;
}

Это незаконно, потому что запятая между 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__)

Последнее решение лучше, требующее только изменений в макроопределении. (Дополнительные скобки вокруг __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");

Если бы я мог получить последний параметр макроса variadic, он бы работал:

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

5

Решение

Нет простого способа получить последний аргумент макроса в полностью общем случае, но вы можете легко реализовать версию, которая получает последний элемент для списков аргументов до некоторого предопределенного максимума. Что-то вроде 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__ ,,,,,,,,,,,)

Вы можете увеличить это количество до конечного размера, сколько захотите, скопировав и вставив несколько минут.

4

Другие решения

Вот мысль:

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

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector