Как реализовать совместимый со стандартом макрос assert с необязательным форматированным сообщением? - PullRequest
0 голосов
/ 31 декабря 2018

Как реализовать стандартно совместимый макрос assert с необязательным форматированным сообщением?

То, что у меня работает, работает в clang, но (правильно) вызывает предупреждение -Wgnu-zero-variadic-macro-arguments, если оно включено (например,через -Wpedantic) когда макрос используется без опционального сообщения. Wandbox

#define MyAssert(expression, ...)                                      \
    do {                                                               \
        if(!(expression))                                              \
        {                                                              \
            printf("Assertion error: " #expression " | " __VA_ARGS__); \
            abort();                                                   \
        }                                                              \
    } while(0)

Ответы [ 3 ]

0 голосов
/ 31 декабря 2018

Основным решением является использование << для cerr:

#define MyAssert(expression, msg)                                  \
do {                                                               \
    if(!(expression))                                              \
    {                                                              \
        std::cerr << msg;                                          \
        abort();                                                   \
    }                                                              \
} while(0)

В этом решении используются потоки C ++, поэтому вы можете форматировать вывод по своему усмотрению.На самом деле это упрощенное решение C ++ 17, которое я использую, чтобы избежать временных (люди обычно используют + вместо << с этим решением, вызывая некоторые предупреждения об эффективности).

Использованиетогда это выглядит так:

MyAssert(true, "message " << variable << " units");

Я думаю, что опциональность здесь фиктивна, так как вы выводите «Ошибка подтверждения:», что означает, что вы ожидаете сообщение.

0 голосов
/ 31 декабря 2018

У меня есть решение, которым я не особо горжусь ..

Мы можем получить первый аргумент в простой форме и в виде строки, используя:

#define VA_ARGS_HEAD(N, ...) N
#define VA_ARGS_HEAD_STR(N, ...) #N

Обратите внимание, что виспользование, чтобы не получать предупреждения, вы должны сделать VA_ARGS_HEAD(__VA_ARGS__, ) (с дополнительными ,), чтобы VA_ARGS_HEAD никогда не использовался с одним параметром (трюк взят из ответа StoryTeller ).

Мы определяем следующую вспомогательную функцию:

#include <stdarg.h>
#include <stdio.h>

inline int assertionMessage(bool, const char *fmt, ...)
{
    int r;
    va_list ap;
    va_start(ap, fmt);
    r = vprintf(fmt, ap);
    va_end(ap);
    return r;
}

Когда утверждение имеет строку формата, функция будет работать с __VA_ARGS__ как есть, однако, когда bool является единственным аргументоммы пропустили строку формата.Вот почему мы добавим еще одну пустую строку после __VA_ARGS__ при ее вызове:

#define MyAssert(...)                                                          \
    do {                                                                       \
        if(!(VA_ARGS_HEAD(__VA_ARGS__, )))                                     \
        {                                                                      \
            printf("Assertion error: %s | ", VA_ARGS_HEAD_STR(__VA_ARGS__, )); \
            assertionMessage(__VA_ARGS__, "");                                 \
            abort();                                                           \
        }                                                                      \
    } while(0)

Обратите внимание, что assertionMessage не имеет printf в названии.Это сделано намеренно, чтобы компилятор не выдавал предупреждения, связанные со строкой формата, для своих вызовов с дополнительным аргументом "".Обратной стороной этого является то, что мы не получаем предупреждений, связанных со строкой формата, когда они полезны.

0 голосов
/ 31 декабря 2018

Нужно по-настоящему использовать препроцессор по максимуму, чтобы отличать дополнительные аргументы от случая, когда они присутствуют.Но с Boost.PP можно сделать это:

#include <boost/preprocessor/variadic/size.hpp>
#include <boost/preprocessor/arithmetic/sub.hpp>
#include <boost/preprocessor/logical/bool.hpp>
#include <boost/preprocessor/cat.hpp>


#define MyAssert(...) BOOST_PP_CAT(MY_ASSERT,BOOST_PP_BOOL(BOOST_PP_SUB(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)))(__VA_ARGS__)

#define MY_ASSERT0(expr) MY_ASSERT1(expr,)

#define MY_ASSERT1(expression, ...)                                    \
    do {                                                               \
        if(!(expression))                                              \
        {                                                              \
            std::printf("Assertion error: " #expression " | " __VA_ARGS__); \
            std::abort();                                              \
        }                                                              \
    } while(0)

MyAssert должен принять хотя бы один аргумент (стандартный).Затем мы посчитаем аргументы, вычтем один и обратимся к логическому (0 или 1).Эти 0 или 1 объединяются с токеном MY_ASSERT для формирования имени макроса, к которому мы переходим к пересылке аргументов.

MY_ASSERT1 (с аргументами) - ваш исходный макрос.MY_ASSERT0 заменяет себя на MY_ASSERT1(expr,), запятая означает, что мы передаем другой аргумент (таким образом выполняя требование для одного дополнительного аргумента), но это пустая последовательность токенов, поэтому она ничего не делает.

Выможет увидеть его вживую .


Так как мы уже спустились по этой кроличьей норе, если кто-то не хочет втягивать Boost.PP, вышеизложенное можно выполнить обычным аргументомтрюк, слегка адаптированный.Во-первых, мы должны определить максимальный предел для аргументов, которые мы допускаем.Я выбрал 20, вы можете выбрать больше.Нам понадобится типичный макрос CONCAT и этот макрос здесь:

#define HAS_ARGS(...) HAS_ARGS_(__VA_ARGS__,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,)
#define HAS_ARGS_(a1,a2,a3,a4,a5,b1,b2,b3,b4,b5,c1,c2,c3,c4,c5,d1,d2,d3,d4,d5,e, N, ...) N

Это подсчет аргументов, но с изюминкой.Когда __VA_ARGS__ является единственным аргументом (без дополнительных), N разрешается как 0. В противном случае он разрешается как 1. После выражения может быть до 20 дополнительных аргументов, любое число из которых будет преобразовано вто же самое 1. Теперь мы просто подключаем его к тому же месту, где мы использовали повышение раньше:

#define MyAssert(...) CONCAT(MY_ASSERT, HAS_ARGS(__VA_ARGS__))(__VA_ARGS__)

Вы можете повозиться с этим здесь

...