функция утверждения

Aug 16 2020

В c есть макрос, assertи хотя он работает, мне он не нравится, главным образом потому, что он не имеет хорошего способа печати строки. В результате я попытался создать лучшую версию assertдля c ++. Однако я перепроектировал его, оставив мне кое-что, что замедляет время компиляции, и кое-что, что я бы не использовал. Как я могу это улучшить?

assert.hh

#ifndef ASSERT_HH
#define ASSERT_HH
#include <iostream>
#include <utility>
#include <tuple>

namespace turtle
{
    /* these macros are needed because #__VA_ARGS__ will include the comma in the string
     * for example: macro(...) __VA_ARGS__
     * macro(a,b) -> expands to "a,b"
     * instead of "a","b"
     */
#define TURTLE_FIRST_(a, ...) a
#define TURTLE_SECOND_(a, b, ...) b

#define TURTLE_FIRST(...) TURTLE_FIRST_(__VA_ARGS__,)
#define TURTLE_SECOND(...) TURTLE_SECOND_(__VA_ARGS__,)

#define TURTLE_EMPTY()

#define TURTLE_EVAL(...) TURTLE_EVAL1024(__VA_ARGS__)
#define TURTLE_EVAL1024(...) TURTLE_EVAL512(TURTLE_EVAL512(__VA_ARGS__))
#define TURTLE_EVAL512(...) TURTLE_EVAL256(TURTLE_EVAL256(__VA_ARGS__))
#define TURTLE_EVAL256(...) TURTLE_EVAL128(TURTLE_EVAL128(__VA_ARGS__))
#define TURTLE_EVAL128(...) TURTLE_EVAL64(TURTLE_EVAL64(__VA_ARGS__))
#define TURTLE_EVAL64(...) TURTLE_EVAL32(TURTLE_EVAL32(__VA_ARGS__))
#define TURTLE_EVAL32(...) TURTLE_EVAL16(TURTLE_EVAL16(__VA_ARGS__))
#define TURTLE_EVAL16(...) TURTLE_EVAL8(TURTLE_EVAL8(__VA_ARGS__))
#define TURTLE_EVAL8(...) TURTLE_EVAL4(TURTLE_EVAL4(__VA_ARGS__))
#define TURTLE_EVAL4(...) TURTLE_EVAL2(TURTLE_EVAL2(__VA_ARGS__))
#define TURTLE_EVAL2(...) TURTLE_EVAL1(TURTLE_EVAL1(__VA_ARGS__))
#define TURTLE_EVAL1(...) __VA_ARGS__

#define TURTLE_DEFER1(m) m TURTLE_EMPTY()
#define TURTLE_DEFER2(m) m TURTLE_EMPTY TURTLE_EMPTY()()

#define TURTLE_IS_PROBE(...) TURTLE_SECOND(__VA_ARGS__, 0)
#define TURTLE_PROBE() ~, 1

#define TURTLE_CAT(a, b) a ## b

#define TURTLE_NOT(x) TURTLE_IS_PROBE(TURTLE_CAT(TURTLE__NOT_, x))
#define TURTLE__NOT_0 TURTLE_PROBE()

#define TURTLE_BOOL(x) TURTLE_NOT(TURTLE_NOT(x))

#define TURTLE_IF_ELSE(condition) TURTLE__IF_ELSE(TURTLE_BOOL(condition))
#define TURTLE__IF_ELSE(condition) TURTLE_CAT(TURTLE__IF_, condition)

#define TURTLE__IF_1(...) __VA_ARGS__ TURTLE__IF_1_ELSE
#define TURTLE__IF_0(...)             TURTLE__IF_0_ELSE

#define TURTLE__IF_1_ELSE(...)
#define TURTLE__IF_0_ELSE(...) __VA_ARGS__

#define TURTLE_COMMA ,

#define TURTLE_HAS_ARGS(...) TURTLE_BOOL(TURTLE_FIRST(TURTLE__END_OF_ARGUMENTS_ __VA_ARGS__)())
#define TURTLE__END_OF_ARGUMENTS_() 0

#define TURTLE_MAP(m, first, ...)                               \
  m(first)                                                      \
  TURTLE_IF_ELSE(TURTLE_HAS_ARGS(__VA_ARGS__))(                 \
    TURTLE_COMMA TURTLE_DEFER2(TURTLE__MAP)()(m, __VA_ARGS__)   \
  )(                                                            \
    /* Do nothing, just terminate */                            \
  )
#define TURTLE__MAP() TURTLE_MAP
#define TURTLE__STRINGIZE(x) TURTLE___STRINGIZE(x)
#define TURTLE___STRINGIZE(x) #x
#define TURTLE_STRINGIZE(x) __FILE__ " line " TURTLE__STRINGIZE(__LINE__) ": assertion {" #x

    template<typename... Args, typename Text>
    constexpr auto assert_basic(const std::tuple<Args...> &arg, const Text &text)
    {
        if constexpr (sizeof...(Args) == 1) {
            if (!std::get<0>(arg)) {
                std::cerr << text << "} failed\n";
                return true;
            }
        } else {
            if (!std::get<0>(arg)) {
                std::cerr << text << "} failed -> "
                          << std::get<1>(arg) << '\n';
                return true;
            }
        }
        return false;
    }

    template<std::size_t Index, typename Result, typename... Args>
    constexpr auto assert_tuple_args_helper(const Result &result, const std::tuple<Args...> &tuple_args_basic)
    {
        if constexpr (Index >= sizeof...(Args)) {
            return result;
        } else if constexpr (Index + 1 >= sizeof...(Args) &&
                             std::is_convertible_v<std::tuple_element_t<Index, std::decay_t<decltype(tuple_args_basic)>>, int>) {
            return assert_tuple_args_helper<Index + 1>(std::tuple_cat(result,
                                                                      std::tuple<decltype(std::tuple{
                                                                              std::get<Index>(tuple_args_basic)})>{
                                                                              std::tuple{std::get<Index>(
                                                                                      tuple_args_basic)}}),
                                                       tuple_args_basic);
        } else if constexpr (
                std::is_convertible_v<std::tuple_element_t<Index, std::decay_t<decltype(tuple_args_basic)>>, int>
                &&
                std::is_convertible_v<std::tuple_element_t<Index + 1, std::decay_t<decltype(tuple_args_basic)>>, int>) {
            return assert_tuple_args_helper<Index + 1>(
                    std::tuple_cat(result, std::tuple<decltype(std::tuple{std::get<Index>(tuple_args_basic)})>{
                            std::tuple{std::get<Index>(tuple_args_basic)}}), tuple_args_basic);
        } else if constexpr (
                std::is_convertible_v<std::tuple_element_t<Index, std::decay_t<decltype(tuple_args_basic)>>, int>
                && !std::is_convertible_v<std::tuple_element_t<
                        Index + 1, std::decay_t<decltype(tuple_args_basic)>>, int>) {
            return assert_tuple_args_helper<Index + 2>(
                    std::tuple_cat(result, std::tuple<decltype(std::tuple{std::get<Index>(tuple_args_basic),
                                                                          std::get<Index + 1>(
                                                                                  tuple_args_basic)})>
                            {std::tuple{std::get<Index>(tuple_args_basic),
                                        std::get<Index + 1>(
                                                tuple_args_basic)}}), tuple_args_basic);
        }
    }

    template<std::size_t TupleIndex, typename Result, typename... Args>
    constexpr auto assert_tuple_text_indices_helper(const Result& result, std::size_t old_index, const std::tuple<Args...>& tuple)
    {
        if constexpr (TupleIndex < std::tuple_size_v<std::decay_t<decltype(tuple)>>) {
            std::size_t next_index = old_index + std::tuple_size_v<std::decay_t<decltype(std::get<TupleIndex>(tuple))>>;
            return assert_tuple_text_indices_helper<TupleIndex+1>(std::tuple_cat(result, std::tuple{old_index}), next_index, tuple);
        } else {
            return result;
        }
    }

    template<typename Text, typename... Args>
    constexpr auto assert(const Text& text, Args &&... args)
    {
        const auto &tuple_args = assert_tuple_args_helper<0>(std::tuple{}, std::tuple{args...});
        constexpr auto tuple_text_indices = assert_tuple_text_indices_helper<0>(std::tuple{}, 0, decltype(tuple_args){});
        [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
            if ((assert_basic(std::get<Indices>(tuple_args), text[std::get<Indices>(tuple_text_indices)]) | ...)) {
                std::exit(1);
            }
        }(std::make_index_sequence<std::tuple_size_v<std::decay_t<decltype(tuple_args)>>>{});
    }
}

#define assert(...) turtle::assert(std::array{TURTLE_EVAL(TURTLE_MAP(TURTLE_STRINGIZE, __VA_ARGS__))}, __VA_ARGS__)

#endif /* ASSERT_HH */

main.cc

#include "assert.hh"


/* testing */
int main()
{
    int a, b, c, d;
    std::cin >> a >> b >> c >> d;
    assert(__LINE__ % 2 == 1, a < b, "a must be less than b", b < c, "b must be less than c", c < d, "c must be less than d");
    assert(__LINE__ % 2 == 1, __FILE__[2] < 80);
}

Ответы

2 Quuxplusone Aug 16 2020 at 01:54

Когда вы ищете узкие места в производительности, может быть полезно сосредоточиться на названных частях TURTLE. ;) Но я даже не думаю, что это твоя главная проблема.

Похоже, все, что вам нужно сделать, это прикрепить сообщение к утверждению, верно?

#define myAssert(x, msg) assert((x) && msg)

Это работает до тех пор, пока вы сознательно всегда удваиваете круглые скобки при вызовах, которые требуют двойных скобок:

myAssert(x < 2, "x is too large");
myAssert((foo<int,int> < 2), "foo<int,int> is too large");

Если вы хотите удалить скобки, я бы сократил эту проблему до "Как мне извлечь последний аргумент макроса из пакета?" Один разумный ответчик предлагает вам снять этот вопрос и просто указать сообщение в качестве первого аргумента:

#define myAssert(msg, ...) assert((__VA_ARGS__) && msg)
myAssert("x is too large", x < 2);
myAssert("foo<int,int> is too large", foo<int,int> < 2);