2016-11-08 105 views
2

我有一个特定的情况,我想在编译时准备一些运行时结构而不需要重复代码。使用模板在编译时填充运行时数据

我有使用在编译时注册一些类型的编译器我写了两个结构:

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宏也注册运行时信息。我还没有拿出任何东西,但也许有一个聪明的方法来管理这件事。

回答

2

如果你不是特别关注注册您的运行时数据到地图中的表现,你可以简单地使用inline函数返回一个static地图实例的引用,并生成“虚拟”registrar实例在您的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"); 
} 

Full example on wandbox


注:

  • 你可能会重复注册,如果同一REGISTER_TYPE包括在多个翻译单元。

  • CAT3(unused_registrar_, __LINE__, type)用于生成不会与其他REGISTER_TYPE展开相冲突的唯一名称。

+0

行号在'unused_registrar_ ## __LINE__ ## type'中不会被__LINE__替代。您必须使用助手宏。 – Leon

+0

@Leon:谢谢,修正。 –

1

我见过你使用C++的扩展__COUNTER__,因此也许你会发现有趣的字符串字面gcc和铿锵的扩展,它可以让你绑定表达注册类型为字符串字面直接向您的Type(无需额外的识别号码):

#include <iostream> 

template <class Char, Char... Cs> 
struct string_literal { 
    static Char str[sizeof...(Cs) + 1]; 
}; 

template <class Char, Char... Cs> 
Char string_literal<Char, Cs...>::str[sizeof...(Cs) + 1] = { Cs..., '\0' }; 

template <class Char, Char... Cs> 
constexpr string_literal<Char, Cs...> operator ""_sl() { 
    return {}; 
} 

template <class T, class SL> 
struct TypeHelper { }; 

template <class T> 
struct Type; 

template <class A, class B> 
auto getType(TypeHelper<A, B>) { 
    return B{}; 
} 

#define REGISTER(TYPE) template <> \ 
    struct Type<TYPE>: TypeHelper<TYPE, decltype(#TYPE##_sl)> { }; 

struct X{}; 

REGISTER(void) 
REGISTER(int) 
REGISTER(X) 

int main(){ 
    std::cout << decltype(getType(Type<void>{}))::str << std::endl; 
} 

输出:

空隙

[live demo]