我有一个特定的情况,我想在编译时准备一些运行时结构,而不需要重复代码。
我有两个结构用于在编译时注册我编写的编译器的某些类型:
using TypeID = u8;
template<typename T, typename TYPE_ID, TYPE_ID I>
struct TypeHelper
{
static constexpr TYPE_ID value = std::integral_constant<TYPE_ID, I>::value;
};
template<typename T> struct Type : TypeHelper<T, u8, __COUNTER__> { static_assert(!std::is_same<T,T>::value, "Must specialize for type!"); };
这些用于配置标头,其中包含一个专门针对我需要的多种类型Type<T>
的宏:
using type_size = unsigned char;
#define GET_NTH_MACRO(_1,_2,_3, NAME,...) NAME
#define REGISTER_TYPE(...) GET_NTH_MACRO(__VA_ARGS__, REGISTER_TYPE3, REGISTER_TYPE2, REGISTER_TYPE1)(__VA_ARGS__)
#define REGISTER_TYPE1(_TYPE_) REGISTER_TYPE2(_TYPE_, _TYPE_)
#define REGISTER_TYPE2(_TYPE_,_NAME_) \
constexpr TypeID TYPE_##_NAME_ = __COUNTER__; \
template<> struct Type<_TYPE_> : TypeHelper<_TYPE_, type_size, TYPE_##_NAME_> { \
static constexpr const char* name = #_NAME_; \
};
REGISTER_TYPE(void)
REGISTER_TYPE(s64)
REGISTER_TYPE(s32)
使这些扩展到
constexpr TypeID TYPE_void = 2;
template<> struct Type<void> : TypeHelper<void, type_size, TYPE_void> { static constexpr const char* name = "void"; };
constexpr TypeID TYPE_s64 = 3;
template<> struct Type<s64> : TypeHelper<s64, type_size, TYPE_s64> { static constexpr const char* name = "s64"; };
constexpr TypeID TYPE_s32 = 4;
template<> struct Type<s32> : TypeHelper<s32, type_size, TYPE_s32> { static constexpr const char* name = "s32"; };
这工作正常,但编译器还需要一些关于这些类型的运行时信息,所以除此之外我必须定义一些辅助函数,如
static TypeID typeForIdent(const std::string& name);
static const char* nameForType(TypeID type);
static void mapTypeName(TypeID type, const std::string& name);
inline bool isSigned(TypeID type)
{
return type == Type<s8>::value || type == Type<s16>::value ||
type == Type<s32>::value || type == Type<s64>::value;
}
和类似的功能。
这些函数必须在没有模板参数的情况下工作,因此TypeID
必须是正常参数。但是我需要在单独的代码部分初始化这些数据,例如:
mapTypeName(Type<s32>::value, "s32");
使用静态std::unordered_map<TypeID, std::string>
。当然,这也意味着当大多数信息在编译时通过类型定义可用时,我必须保持两倍的代码。
我想知道是否有一些我不知道的模糊技巧可以合并这些技巧,以便REGISTER_TYPE
宏也注册运行时信息。我还没有任何东西,但也许有一种聪明的方法可以解决这个问题。
答案 0 :(得分:2)
如果您不特别关注将运行时数据注册到地图中的性能,则可以使用inline
函数返回对static
地图实例的引用,并生成您在registrar
宏中的&#34;虚拟&#34; REGISTER_TYPE
个实例,用于在其构造函数中填充地图。
inline auto& registration_map()
{
static std::unordered_map<int, std::string> m;
return m;
}
struct registrar
{
registrar(int id, std::string s)
{
registration_map()[id] = std::move(s);
}
};
template <typename T>
struct TypeHelper { };
#define CAT3_IMPL(a, b, c) a ## b ## c
#define CAT3(a, b, c) CAT3_IMPL(a, b, c)
#define REGISTER_TYPE(id, type) \
template<> struct TypeHelper<type> { }; \
[[maybe_unused]] registrar CAT3(unused_registrar_, __LINE__, type) {id, #type};
REGISTER_TYPE(0, int)
REGISTER_TYPE(1, float)
REGISTER_TYPE(2, double)
int main()
{
assert(registration_map()[0] == "int");
assert(registration_map()[1] == "float");
assert(registration_map()[2] == "double");
}
注意:
如果多个翻译单元中包含相同的REGISTER_TYPE
,您可能会重复注册。
CAT3(unused_registrar_, __LINE__, type)
用于生成不会与其他REGISTER_TYPE
扩展发生冲突的唯一名称。
答案 1 :(得分:1)
我已经看到你使用c ++扩展__COUNTER__
,因此你可能会发现有趣的字符串文字gcc和clang扩展,它允许你将表达注册类型的字符串文字直接绑定到你的{{1} }(没有额外的识别号码):
Type
输出:
空隙