2016-11-08 2 views
2

Ich habe eine bestimmte Situation, in der ich einige Laufzeitstrukturen zur Kompilierzeit vorbereiten möchte, ohne Code duplizieren zu müssen.Füllen Laufzeitdaten zur Kompilierzeit mit Vorlagen

Ich habe zwei structs, die ich bei der Kompilierung für einen Compiler einige Typen verwenden, um zu registrieren, schrieb ich:

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!"); }; 

Diese werden in einer Config-Header mit einem Makro verwendet, die Type<T> für mehrere Arten spezialisieren Ich benötige:

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) 

, so dass diese zu erweitern, um

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"; }; 

Das funktioniert gut, aber der Compiler erfordert auch eine gewisse Laufzeit Informationen über diese Art, so dass zusätzlich zu diesem I

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; 
} 

und ähnlichen Funktionen einiger Zusatzfunktionen wie definieren.

Diese Funktionen müssen ohne Template-Argumente funktionieren, so dass TypeID ein normales Argument sein muss. Aber ich bin erforderlich, um solche Daten in einem separaten Teil des Codes zu initialisieren, zB:

mapTypeName(Type<s32>::value, "s32"); 

, die ein statisches std::unordered_map<TypeID, std::string> verwendet. Natürlich impliziert dies auch, dass ich den Code doppelt halten muss, wenn die meisten Informationen bereits zur Kompilierzeit durch die definierten Typen verfügbar sind.

Ich frage mich, ob es einen obskuren Trick gibt, den ich vermisse, der diese verschmelzen könnte, so dassMakro auch die Laufzeitinformationen registriert. Ich bin noch nicht mit etwas gekommen, aber vielleicht gibt es einen cleveren Weg, das zu bewerkstelligen.

Antwort

2

Wenn Sie sich nicht besonders mit der Leistung sind Ihre Laufzeitdaten in der Karte von der Registrierung können Sie einfach eine inline Funktion verwenden, die einen Verweis auf eine static Karte Instanz zurückgibt, und erzeugen „Dummy“registrar Instanzen in Ihrem REGISTER_TYPE Makro, die die Karte in ihrem Konstruktor ausfüllen.


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.


Hinweise:

  • Sie wiederholte Registrierungen erhalten können, wenn die gleiche REGISTER_TYPE in mehreren Übersetzungseinheiten enthalten.

  • CAT3(unused_registrar_, __LINE__, type) wird verwendet, um einen eindeutigen Namen zu generieren, der nicht mit anderen Erweiterungen kollidiert REGISTER_TYPE.

+0

Die Zeilennummer wird für '' __LINE__' in unused_registrar_ __LINE__ ## ## Baumuster zur nicht substituiert. Sie müssen ein Hilfsmakro verwenden. – Leon

+0

@Leon: Danke, behoben. –

1

Ich habe Sie C++ Erweiterung verwenden gesehen __COUNTER__ und als solche vielleicht werden Sie interessante Stringliteral gcc und Klirren Erweiterung finden, die Sie erlauben würde Stringliteral registrierte Typ zu binden exprimierenden direkt auf Ihrem Type (ohne zusätzliche Identifikationsnummer):

#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; 
} 

Ausgang:

Leere

[live demo]

Verwandte Themen