模板与C ++错误代码Boilerplate的宏

时间:2018-06-06 20:46:01

标签: c++ templates macros code-generation error-code

TL; DR - 可以使用模板实现样板生成宏吗?

我有一些代码使用了C ++ std :: error_code和error_category类。我发现,一旦错误代码的数量开始增加,为每个代码编写的样板数量也会迅速增长。

为了解决这个问题,我已经编写了一些宏,这些宏应该静态检查并从我们真正关心的事物中生成大部分样板 - 枚举代码和附加到它们的消息。这些宏将枚举和消息作为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 :(得分:2)

需要你的宏来对字符串进行字符串化,并且有助于避免在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)

Demo