Шаблоны против макросов для кода ошибки C ++ - PullRequest
0 голосов
/ 06 июня 2018

TL; DR - можно ли реализовать шаблон генерации макроса с использованием шаблонов?

У меня есть код, который использует классы C ++ std :: error_code и error_category.Я обнаружил, что как только количество кодов ошибок начало расти, количество написанного для каждого из них шаблона также росло очень быстро.

Чтобы обойти это, я написал несколько макросов, которые должны статически проверять и генерировать большую часть шаблонов из того, что нас действительно волнует - коды enum и прикрепленные к ним сообщения.Эти макросы принимают перечисление и сообщения в виде const std :: map.

Мой вопрос - можно ли заменить этот тип генерации шаблонов каким-либо шаблоном?Прямо сейчас, если это не сработало, совет goto для других был «проверять static_asserts», что делает его немного PITA для использования.

Если его нельзя просто заменить шаблонами, можно ли добавить код для улучшения вывода неудачной компиляции, чтобы их было менее болезненно использовать?Сейчас неудачная компиляция выводит как статические утверждения, так и множество других ненужных выходных данных.

Я включил ниже некоторый код, который демонстрирует макросы - я удалил весь наш связанный с пространством имен код, так что он может быть немного неправильным, но должен продемонстрировать цель достаточно хорошо.

//C++14 definition - we are using C++11
template< bool B, class T = void >
using enable_if_t = typename std::enable_if<B,T>::type;

//Generic template test for any other type
template <typename T, typename = void>
struct is_std_map : std::false_type {};

//Specialised test for a std::map type
template <typename T>
struct is_std_map<T, enable_if_t<
                    std::is_same<typename T::value_type,
                                std::pair<const typename T::key_type,
                                          typename T::mapped_type>
                    >::value>
> : std::true_type {};

#define MAKE_ERROR_CODE_CATEGORY(EC, EC_MESSAGE_MAP)               \
/* Check that we have an enum type as the first arg, and a const std::map arg for the second */                 \
static_assert(std::is_enum<EC>::value, "!");                                             \
static_assert(std::is_const<decltype(EC_MESSAGE_MAP)>::value, "!" );                \
static_assert(is_std_map<decltype(EC_MESSAGE_MAP)>::value, "!");        \
/* Validate that the non-const types for our EC and our EC_MESSAGE_MAP are as expected*/                        \
static_assert(std::is_same<                                                                                     \
                    std::remove_const<EC>::type,                                                                \
                    std::remove_const<decltype(EC_MESSAGE_MAP)::key_type                                        \
                >::type>::value,                                                                                \
              "!");                          \
static_assert(std::is_same<                                                                                     \
                    std::remove_const<std::string>::type,                                                       \
                    std::remove_const<decltype(EC_MESSAGE_MAP)::mapped_type                                     \
                >::type>::value,                                                                                \
              "!");  \
/*Generates a standardised category for the provided EC */                                                      \
struct EC## _category : std::error_category                                                                \
{                                                                                                               \
    const char* name() const noexcept override                                                                  \
    {                                                                                                           \
      return  #EC ;                                                                                             \
    }                                                                                                           \
    std::string message(int c) const override                                                                   \
    {                                                                                                           \
        EC code = static_cast<EC>(c);                                                                           \
        auto itr = EC_MESSAGE_MAP.find(code);                                                                   \
        if (itr != EC_MESSAGE_MAP.end())                                                                        \
        {                                                                                                       \
            return itr->second;                                                                                 \
        }                                                                                                       \
        else                                                                                                    \
        {                                                                                                       \
            return "(unrecognized error)";                                                                      \
        }                                                                                                       \
    }                                                                                                           \                                                                                                    \
};
namespace std                                                                                                   \
{                                                                                                               \
    template <>                                                                                                 \
        struct is_error_code_enum< EC > : true_type {};                                   \
}                                                                                                               \
/* Declare a global function returning a static instance of the custom category */                              \
const EC## _category& EC## _category_generator()                     \
{                                                                                                               \
    static EC## _category c;                                                              \
    return c;                                                                                                   \
}                                                                                                               \
/* Adds the standard error code construction call */                                                            \
inline std::error_code make_error_code(EC e)                                              \
{                                                                                                               \
    return {static_cast<int>(e), EC## _category_generator()};                                                   \
}                                 

1 Ответ

0 голосов
/ 07 июня 2018

Ваш макрос необходим для упорядочения перечисления и помогает избежать шаблонов для специализации в std, но вы можете извлечь некоторый код для создания шаблонов:

// Traits to retrieve name and mapping from enum.
template <typename E>
struct enum_traits
{
    static_assert(std::is_enum<E>::value, "!");

    static const char* const name;
    static const std::map<E, std::string> mapping;
};

template <typename E>
struct ECategory_impl : std::error_category
{
    static_assert(std::is_enum<E>::value, "!");

    const char* name() const noexcept override
    {
      return enum_traits<E>::name;
    }

    std::string message(int c) const override
    {
        const auto& Map = enum_traits<E>::mapping;
        E code = static_cast<E>(c);
        auto itr = Map.find(code);
        if (itr != Map.end())
        {
            return itr->second;
        }
        else
        {
            return "(unrecognized error)";
        }
    }
};

template <typename E>
std::error_code make_error_code(E e)
{
    static_assert(std::is_enum<E>::value, "!");
    static const ECategory_impl<E> categ{};
    return {static_cast<int>(e), categ};
}

и затем MACRO (который может быть опущенесли учесть, что теперь количество повторяемых материалов достаточно низкое):

#define MAKE_ERROR_CODE_CATEGORY(E)                      \
/* Stringification for the name*/                        \
template <> const char* const enum_traits<E>::name = #E; \
/* Specialization in std */                              \
namespace std                                            \
{                                                        \
    template <>                                          \
    struct is_error_code_enum<E> : true_type {};         \
}                                                        \
/* Alias for custom naming */                            \
using E##_category = ECategory_impl<E>;
//                                    ^
// You might remove that final ';' for a usage `MAKE_ERROR_CODE_CATEGORY(E);`
// instead of current `MAKE_ERROR_CODE_CATEGORY(E)`

Использование тогда аналогично:

enum class E {A, B};
template <>
const std::map<E, std::string> enum_traits<E>::mapping{
    {E::A, "A"},
    {E::B, "E"}
};

MAKE_ERROR_CODE_CATEGORY(E)

Демо

...