2016-04-07 4 views
3

Und auch ... Ich möchte keine Funktionszeiger verwenden, ich möchte die Funktion selbst direkt verwenden (damit es inline sein kann oder andere Optimierungen zutreffen).Was ist eine Alternative zu Merkmalen für die Auswahl zwischen zwei verschiedenen benannten Funktionen?

Angenommen: Ich habe eine Template-Funktion/Klasse, die einige mathematische Sachen berechnen wird, und der Template-Parameter ist der Integraltyp, der unsigned int32_t oder unsigned int64_t sein könnte.

Irgendwann brauche ich Zufallszahlen, also brauche ich einen Generator, und in dem einen Fall werde ich mt19937 und in den anderen mt19937_64 verwenden. Die Namen der tatsächlichen Typen sind also unterschiedlich, aber ich muss einen auswählen und ihn im Quellcode schreiben.

Offensichtlich würde eine Merkmalsklasse auf dem integralen Typ gut funktionieren (und das mache ich jetzt). Aber es scheint mir für diese einmalige Verwendung Syntax-mäßig schwergewichtig zu sein, und auch etwas nicht-lokal (w.r.t. Lesen des Quellcodes, wenn Sie verstehen, was ich meine).

Ein anderer Ansatz wäre, die Verwendung des Generators in einer (generischen) Funktion einzukapseln und vollständige Spezialisierungen für meine beiden ganzzahligen Typen bereitzustellen. Und das ist eigentlich in Ordnung.

Aber: Gibt es andere Alternativen? Gibt es irgendeine Art von Kompilierzeit "if" oder "switch" (nicht ganz enable-if, die das Instanziieren von Schablonen aktiviert/deaktiviert), die ich hier benutzen kann? Oder etwas anderes (vielleicht sogar einfacher als Template-Metaprogrammierung, die ich gerade nicht sehe)?

(PS Bitte nicht aufgehängt auf mt19937 & mt19937_64 - Ich weiß, diejenigen beide Aliase einen Typs sind ich mich mit meinem integralen Typ instanziiert könnte - aber ich die Standard-Definitionen mit ihrem großen Satz verwendet viel lieber hatte von ganz magischen Zahlen plus ich bin nicht nur interessiert an mt19937/mt19937_64 aber ähnlichen Fällen auch)

Hier ist, was der Code für meine Züge Klasse zur Zeit wie folgt aussieht:..

template <class Base> 
struct traits { }; 

template <> 
struct traits<unsigned __int32> 
{ 
    using base_t = unsigned __int32; 
    static const int nbits = std::numeric_limits<base_t>::digits; 
    using random_engine_t = std::mt19937; 
    ... 
}; 

template <> 
struct traits<unsigned __int64> 
{ 
    using base_t = unsigned __int64; 
    static const int nbits = std::numeric_limits<base_t>::digits; 
    using random_engine_t = std::mt19937_64; 
    ... 
}; 
+1

Bitte geben Sie den Code ein, den Sie für Merkmale verwenden. –

+0

Was stimmt nicht mit den Eigenschaften? Benötigen Sie nur die Eigenschaften :: random_engine_t an genau einer Stelle, so dass Sie das Muster nicht mögen? – Barry

+0

Ziemlich viel, aber auch, dass das Boilerplate vom Point-of-Use entfernt ist. Das heißt, Sie müssen einen Namen für die Abstraktion erfinden und sich daran erinnern, anstatt nur die beiden ursprünglichen Namen in-line/in-place zu verwenden, wo Sie sie verwenden möchten. (Natürlich umgeben von einer zusätzlichen Syntax, die hoffentlich "einigermaßen leicht zu lesen" ist für einige C++ - relative Werte von "vernünftig".) – davidbak

Antwort

3

Ansatz # 1 : erzeuge sauber verteilte Merkmale Arten.

Einige Dienstprogramme:

template<class T>struct tag_t{using type=T;}; 
template<class T>constexpr tag_t<T> tag = {}; 
template<class Tag>using type=typename Tag::type; 

Statt nun Züge Klassen schaffen wir getaggten Überlastungen:

tag<std::mt1337> which_random_engine(tag_t<int>); 
tag<std::mt1337_64> which_random_engine(tag_t<std::int64_t>); 

, die Sie solche Überlastung tun können ... wo auch immer.

Wir können diese verwenden, um eine Züge Klasse zu definieren:

template<class T> 
using random_engine_for = type<decltype(which_random_engine(tag<T>))>; 

Verwendung:

random_engine_for<T> engine; 

Ansatz # 2:

template<class A, class B> struct zpair_t {}; 
template<class T, class...> struct lookup_t {}; 
template<class T, class A, class B, class...Ts> 
struct lookup_t<T, zpair_t<A, B>, Ts...>:lookup_t<T, Ts...>{}; 
template<class T, class B, class...Ts> 
struct lookup_t<T, zpair_t<T, B>, Ts...>:tag_t<B>{}; 
template<class T, class Default, class...Ts> 
struct lookup_t<T, tag_t<Default>, Ts...>:tag_t<Default>{ 
    static_assert(sizeof(Ts...)==0, "Default must be last"); 
}; 

template<class A, class B> using kv=zpair_t<A,B>; 
template<class Default> using otherwise=tag_t<Default>; 
template<class T, class...KVs> 
using lookup = type<lookup_t<T, KVs...>>; 

using random_engine_t = 
    lookup< T, 
    kv< int, std::mt19937 >, 
    kv< std::int64_t, std::mt19937_64 >, 
    otherwise<void> // optional 
    >; 

, die einige der gleichen verwendet Dienstprogramme und führt eine Kompilierzeittypzuordnung durch.

Ich bin sicher, boost hat eine bessere Variation der Syntax.

+0

Im Grunde genommen die gleiche Syntax ... 'mpl :: pair' anstelle von' zpair', 'mpl :: at , T>' anstelle von 'lookup '. – Barry

+0

Der Tag Ansatz ist sehr cool und ich bin sehr zufrieden mit Tag Versand. Wusste nicht, dass es so leicht verpackt werden könnte. Ich vergleiche und kontrastiere nun mit "std :: conditional". – davidbak

+0

Oh und BTW auch danke für die Erinnerung an Alias ​​Vorlagen, sehr nützlich auch hier. – davidbak

3

könnten Sie std::conditional verwenden:

template <class Int> 
using engine_t = std::conditional_t< 
    std::is_same<Int, uint32_t>{}, 
    std::mt19937, 
    std::mt19937_64 
>; 

Unter der Annahme, dass Int nur uint32_t oder uint64_t sein kann. Dies wird immer komplizierter, je mehr Typen Sie am Ende haben. Es hat auch Sicherheitsprobleme - was ist, wenn jetzt uint16_t unterstützt wird? Du würdest lautlos mit mt19937_64 enden, was wahrscheinlich nicht die richtige Entscheidung ist.


könnten Sie auch die mpl::map Ansatz verwenden:

using engine_map = mpl::map< 
    mpl::pair<uint32_t, std::mt19937>, 
    mpl::pair<uint64_t, std::mt19937_64> 
>; 

template <class Int> 
using engine_t = typename mpl::at<engine_map, Int>::type; 

Dies zu mehr Arten besser verläuft. Außerdem wäre ein schwerer Kompilierfehler, wenn Sie einen neuen Typ einführen, der wahrscheinlich ein besserer Ansatz ist.


Ob dies besser oder schlechter als eine Züge Klasse ist denke ich eine Frage der Präferenz und hängt von dem Rest des Projekts.

+0

Ich hatte von 'std :: conditional' nichts gewusst, was sehr gut in Verbindung mit einem lokalen Typ-Alias ​​(' using') klingt. 'mpl :: map' ist es wert zu wissen, aber wahrscheinlich ist es hier zu viel. – davidbak

+0

@davidbak Es ist das erste, woran ich dachte, aber es scheint die schlechteste Option zu sein, wenn man zusätzlich 'mpl :: map' und Yakks zwei Lösungen in Betracht zieht. – Barry

+0

Hallo, ich sehe, du hattest auch eine Alias-Vorlage und ich habe es nicht einmal bemerkt. Zeichen der guten Syntax, denke ich, wenn Sie gerade verstehen, was gemeint ist und es völlig natürlich aussieht ... – davidbak

Verwandte Themen