2016-11-08 81 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]