用于 C++ 错误代码样板的模板与宏
Templates Vs Macros for C++ Error Code Boilerplate
TL;DR - 可以使用模板实现样板生成宏吗?
我有一些使用 C++ std::error_code 和 error_category 类 的代码。我发现一旦错误代码的数量开始增加,为每个错误代码编写的样板文件的数量也会迅速增加。
为了解决这个问题,我编写了一些宏,这些宏应该静态检查并根据我们真正关心的事情生成大部分样板 - 枚举代码和附加到它们的消息。这些宏将枚举和消息作为 const std::map.
我的问题 - 这种样板生成可以用某种模板代替吗?现在,如果这失败了,其他人的 goto 建议已经 "check for 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()}; \
}
需要您的宏来对枚举进行字符串化,并有助于避免 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)
TL;DR - 可以使用模板实现样板生成宏吗?
我有一些使用 C++ std::error_code 和 error_category 类 的代码。我发现一旦错误代码的数量开始增加,为每个错误代码编写的样板文件的数量也会迅速增加。
为了解决这个问题,我编写了一些宏,这些宏应该静态检查并根据我们真正关心的事情生成大部分样板 - 枚举代码和附加到它们的消息。这些宏将枚举和消息作为 const std::map.
我的问题 - 这种样板生成可以用某种模板代替吗?现在,如果这失败了,其他人的 goto 建议已经 "check for 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()}; \
}
需要您的宏来对枚举进行字符串化,并有助于避免 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)