2015-06-12 10 views
11

Ich sehe zwei mögliche Stile für die Implementierung von Typlisten in C++ 11/14 und ich war neugierig, ob es einen Grund gibt, einen vor dem anderen zu bevorzugen. Die erste Technik ist outlined here und modelliert sich selbst in Boosts MPL-Bibliothek. In diesem Stil definieren Sie Meta 'freie Funktionen' (oberste Ebene mit Deklarationen), die Typlisten aufnehmen und auf ihnen arbeiten. Hier ist, wie Sie eine Meta-Version von std :: implementieren würden transformieren, die auf Typen anstelle von Werten in der ersten Art arbeitet:C++ 14 Typlisten, irgendein Grund, "freie Funktionen" zu "Methoden" zu bevorzugen oder umgekehrt?

template <typename... Args> 
    struct type_list; 

    namespace impl 
    { 
     template <template <typename...> class F, class L> 
     struct transform_impl; 

     template <template <typename...> class F, template <typename...> class L, typename... T> 
     struct transform_impl<F, L<T...>> 
     { 
      using type = L<typename F<T>::type...>; 
     }; 
    } 

    template <template <typename...> class F, class L> 
    using transform = typename impl::transform_impl<F, L>::type; 

Die zweite Art ist meta ‚Methoden‘ zu definieren (mit Erklärungen innerhalb der Typenliste Struktur). Hier ist, wie Transformation sieht in diesem Stil:

template <typename... Args> 
    struct type_list { 
     // ... other 'methods' 

     template<template<class> class Wrapper> 
     using transform = 
      type_list<Wrapper<Args>...>; 

     // ... other 'methods' 
    }; 

Der Vorteil, den ich in der zweiten Art zu sehen ist, dass Sie immer noch das Args... Parameter Pack zur Verfügung haben, so haben Sie delegieren nicht zu impl Helferfunktionen. Zwei mögliche Nachteile sind: 1) Sie müssen alle Ihre Meta-Funktionen in type_list setzen, anstatt sie möglicherweise in separate Header zu setzen, so dass Sie einige Modularitäten verlieren und 2) die 'freien' Meta-Funktionen auch auf Tupeln und anderen variadischen Vorlagen funktionieren Klasse aus der Box. Ich weiß nicht, wie häufig der Wunsch nach # 2 eigentlich in der Praxis ist, ich habe nur Gelegenheiten gefunden, type_list und tuple selbst zu verwenden, und das Schreiben von Meta-Code zwischen type_list und tuple ist nicht so schwierig.

Gibt es einen guten Grund, den einen oder anderen stark zu bevorzugen? Vielleicht ist # 2 eigentlich ein häufiger Fall?

+7

Es ist 'typename L :: Template-Transformation ' vs. 'transform '. –

+0

Ah, hmm, hatte die Hässlichkeit der Telefonvorwahl nicht berücksichtigt. –

Antwort

7

Der zweite ist aus vielen Gründen schlecht.

Erstens, es anzurufen ist ein Durcheinander. Vorlagen innerhalb von Vorlagen erfordern die Verwendung des Schlüsselworts template.

Zweitens erfordert es, dass Ihre Typenliste jede Operation enthält, die Sie auf Typenlisten in seinem Körper ausführen möchten. Es ist, als würde man jede Operation auf einer string als eine Methode für die Zeichenfolge definieren: Wenn Sie freie Funktionen zulassen, können neue Operationen erstellt werden, und Sie können sogar Überschreibungen implementieren.

Schließlich betrachten die ::type versteckt:

Beginnen Sie mit diesen Primitiven:

template<class T>struct tag{using type=T;}; 
template<class Tag>using type_t=typename Tag::type; 
template<class...Ts>struct types : tag<types<Ts...>>{}; 

Transformation oder fmap, wie sieht dann: kann

template<template<class...>class Z, class Types> 
struct fmap; 
template<template<class...>class Z, class...Ts> 
struct fmap<Z, types<Ts...>>:types<Z<Ts...>>{}; 
template<template<class...>class Z, class Types> 
using fmap_t = type_t<fmap<Z,Types>>; 

und Sie entweder type_t<fmap<Z,types<int,double>>> verwenden, oder fmap_t<Z,types<int,double>>, um die Typen des zugeordneten Typs abzurufen.

Ein weiterer Ansatz ist constexpr Funktionen zu verwenden, die verschiedene Dinge enthalten:

template<class T>struct tag{using type=T;}; 
template<class...>struct types{using type=types;}; 
template<class Tag>using type_t=typename Tag::type; 

template<template<class...>class Z> 
struct z {template<class...Ts>using apply=Z<Ts...>; constexpr z(){};}; 
template<class...Ts> 
struct one_type {}; 
template<class T0> 
struct one_type<T0> { using type=T0; }; 
template<class...Ts> 
using one_type_t=typename one_type<Ts...>::type; 

template<template<class>class Z> 
struct z_one_base { 
    template<class...Ts> 
    using helper = Z<one_type_t<Ts...>>; 
    using type = z<helper>; 
}; 
template<template<class>class Z> 
using z_one = type_t<z_one_base<Z>>; 

jetzt fmap ist einfach:

// take a template metafunction and a list of types 
// and apply the metafunction to each type, returning the list 
template<template<class...>class Z, class...Ts> 
constexpr auto fmap(z<Z>, types<Ts...>) 
-> types<Z<Ts>...> { return {}; } 

und andere Funktionen folgen:

// a template metafunction and a list of types 
// and apply the template metafunction to all of the types 
template<template<class...>class Z, class...Ts> 
constexpr auto apply(z<Z>, types<Ts...>) 
-> tag<Z<Ts...>> { return {}; } 

// take any number of tags 
// and make a type list from them 
template<class...Tags> 
constexpr auto make_list(Tags...) 
-> types<type_t<Tags>...> { return {}; } 

// concat of nothing is an empty list 
constexpr types<> concat() { return {}; } 
// concat of a list alone is a list alone: 
template<class...T1s> 
constexpr auto concat(types<T1s...>) 
->types<T1s...>{ return {}; } 
// concat of 2 or more lists is the concat of the first two, 
// concatted with the rest 
template<class...T1s, class...T2s, class...Types> 
constexpr auto concat(types<T1s...>,types<T2s...>,Types...) 
->decltype(concat(types<T1s...,T2s...>{},Types{}...)) 
{ return {}; } 


// take a tagged list or a tagged type, and return a list 
template<class T> 
constexpr auto fbox(tag<T>)->types<T> { return {}; } 
template<class...Ts> 
constexpr auto fbox(tag<types<Ts...>>)->types<Ts...> { return {}; } 

// create z_ versions of functions above: 
#define CAT2(A,B) A##B 
#define CAT(A,B) CAT2(A,B) 
// lift functions to metafunctions with z_ prefix: 
#define Z_F(F) \ 
    template<class...Ts> \ 
    using CAT(meta_, F) = decltype(F(Ts{}...)); \ 
    using CAT(CAT(z_, F),_t) = z<CAT(meta_, F)>; \ 
    static constexpr CAT(CAT(z_, F),_t) CAT(z_, F){} 

Z_F(concat); 
//Z_F(apply); 
//Z_F(fmap); 
Z_F(fbox); 
static constexpr z_one<tag> z_tag{}; 


// joins a list of lists or types into a list of types 
template<class...Ts> 
constexpr auto join1(types<Ts...>) 
->type_t<decltype(apply(z_concat, fmap(z_fbox, types<tag<Ts>...>{})))> 
{ return {}; } 
template<class Types> 
constexpr auto join(Types types) 
->type_t<decltype(apply(z_concat, fmap(z_fbox, fmap(z_tag, types))))> 
{ return {}; } 

template<class Z, class...Ts> 
constexpr auto fbind(Z z, Ts...ts) 
->decltype(join(fmap(z, ts...))) 
{ return {}; } 

und Arbeit mit Pseudo-Typen (tag s) statt mit Typen direkt auf der obersten Ebene.Wenn Sie zu Typen mit type_t zurückkehren möchten, wenn Sie möchten.

Ich denke, das ist ein boost::hana wie Ansatz, aber ich habe nur angefangen zu betrachten boost::hana. Der Vorteil hier ist, dass wir die Typenbündel von den Operationen entkoppeln, wir erhalten Zugang zu vollständiger C++ - Überladung (anstelle des Vorlagenmusterabgleichs, der fragiler sein kann), und wir können den Inhalt der Typenbündel direkt ableiten, ohne dass Mach die using und leere-primäre Spezialisierung Tricks.

Alles, was verbraucht wird, ist ein verpackter Typ von tag<?> oder types<?> oder z<?>, also nichts ist "echt".

Prüfregeln:

template<class T> using to_double = double; 
template<class T> using to_doubles = types<double>; 

int main() { 
    types< int, int, int > three_ints; 

    auto three_double = fmap(z_one<to_double>{}, three_ints); 
    three_double = types<double, double, double >{}; 
    auto three_double2 = join(fmap(z_one<to_doubles>{}, three_ints)); 
    three_double = three_double2; 
    auto three_double3 = fbind(z_one<to_doubles>{}, three_ints); 
    three_double3 = three_double2; 
} 

Live example.

+0

Ich benutze die zweite, wenn ich schnell etwas für SO kochen will, und das ist es so ziemlich :) –

+0

In Ihrem ersten Code-Block auf der letzten Zeile glaube ich, Sie wollen 'types :: tag' nicht' types: tag' –

+0

@JosephGarvin nein. 'types' erbt von einem' tag' von sich selbst. Der Punkt von "Tag" ist, dass er kein Typ sein kann, sondern ein Tag, der über einen Typ spricht. Es erlaubt Ihnen, von "tag " zu erben, dann kann jemand 'type_t ' 'extrahieren' was auch immer' am anderen Ende. Plus, später kann das 'tag ' an 'constexpr'-Funktionen übergeben und verarbeitet werden, während ein tatsächliches' was auch immer' nicht sein könnte. – Yakk