2014-11-25 11 views
10

Dies ist eher eine konzeptionelle Frage. Ich versuche den einfachsten Weg zu finden, eine Zwei-Arg-Vorlage (die Argumente sind Typen) in eine Ein-Arg-Vorlage umzuwandeln. Das heißt, einen der Typen zu binden.Curry für Vorlagen in C++ Metaprogrammierung

Dies wäre das Meta-Programmierung Äquivalent von bind in Boost/Std. Mein Beispiel enthält einen möglichen Anwendungsfall, bei dem std::is_same als Vorlagenargument an eine Vorlage übergeben wird, die ein Argumentargumentvorlagenargument (std::is_same ist eine Zwei-Arg-Vorlage), d. H. Zu TypeList::FindIf, verwendet. Die TypeList ist hier nicht vollständig implementiert, noch ist FindIf, aber Sie bekommen die Idee. Es nimmt ein "unäres Prädikat" und gibt den Typ zurück, für den dieses Prädikat wahr ist, oder void, wenn nicht ein solcher Typ.

Ich habe 2 Arbeitsvarianten, aber die erste ist kein One-Liner und die zweite verwendet eine ziemlich ausführliche BindFirst Contraption, die nicht für nicht-type Vorlage Argumente funktionieren würde. Gibt es eine einfache Möglichkeit, einen solchen One-Liner zu schreiben? Ich glaube, die Prozedur, die ich suche, heißt currying.

#include <iostream> 

template<template<typename, typename> class Function, typename FirstArg> 
struct BindFirst 
{ 
    template<typename SecondArg> 
    using Result = Function<FirstArg, SecondArg>; 
}; 

//template<typename Type> using IsInt = BindFirst<_EqualTypes, int>::Result<Type>; 
template<typename Type> using IsInt = std::is_same<int, Type>; 


struct TypeList 
{ 
    template<template<typename> class Predicate> 
    struct FindIf 
    { 
     // this needs to be implemented, return void for now 
     typedef void Result; 
    }; 
}; 

int main() 
{ 

    static_assert(IsInt<int>::value, ""); 
    static_assert(!IsInt<float>::value, ""); 


    // variant #1: using the predefined parameterized type alias as predicate 
    typedef TypeList::FindIf<IsInt>::Result Result1; 

    // variant #2: one-liner, using BindFirst and std::is_same directly 
    typedef TypeList::FindIf< BindFirst<std::is_same, int>::Result>::Result Result2; 

    // variant #3: one-liner, using currying? 
    //typedef TypeList::FindIf<std::is_same<int, _>>::Result Result2; 

    return 0; 
} 

Klicken Sie here für Code in Online-Compiler Godbolt.

+0

Da ich glaube, dass die Antwort "nein, es gibt keinen einfacheren Weg", werde ich natürlich jede Antwort akzeptieren, die einige nützliche Informationen gibt (wie Pläne, vielleicht solch ein Thi einzuschließen) ng im Standard). – haelix

+1

Antworten auf die Frage ["Wie kann ich variadic Vorlage Template-Parameter curry?"] (Http://stackoverflow.com/q/21406726/3043539) kann hilfreich sein. – Constructor

+1

Inspiration: http://www.boost.org/doc/libs/1_57_0/libs/mpl/doc/refmanual/bind.html – Drax

Antwort

3

Ich denke, der typische Weg dies zu tun ist, alles in der Welt der Typen zu halten. Nimm keine Template-Vorlagen - sie sind chaotisch. Lassen Sie sich eine metafunction schreibt ApplyAnInt genannt, die eine „metafunction Klasse“ übernehmen und anwenden int es:

struct IsInt { 
    template <typename T> 
    using apply = std::is_same<T, int>; 
}; 

static_assert(ApplyAnInt<IsInt>::type::value, ""); 
:

template <typename Func> 
struct ApplyAnInt { 
    using type = typename Func::template apply<int>; 
}; 

Wo eine einfache metafunction Klasse nur Überprüfung sein könnte, wenn der angegebene Typ ein int ist

Ziel ist es nun zu unterstützen:

static_assert(ApplyAnInt<std::is_same<_, int>>::type::value, ""); 

Wir tun können. Wir gehen Arten aufrufen, die _ „Lambda-Ausdrücke“ enthalten, und eine metafunction schreiben lambda genannt, die entweder eine metafunction Klasse weiterleiten, die nicht ein Lambda-Ausdruck ist, oder ein neues metafunction produzieren, wenn er:

template <typename T, typename = void> 
struct lambda { 
    using type = T; 
}; 

template <typename T> 
struct lambda<T, std::enable_if_t<is_lambda_expr<T>::value>> 
{ 
    struct type { 
     template <typename U> 
     using apply = typename apply_lambda<T, U>::type; 
    }; 
}; 

template <typename T> 
using lambda_t = typename lambda<T>::type; 

Also haben wir unsere ursprüngliche metafunction aktualisieren:

template <typename Func> 
struct ApplyAnInt 
{ 
    using type = typename lambda_t<Func>::template apply<int>; 
}; 

Nun, da zwei Dinge lassen: wir brauchen is_lambda_expr und apply_lambda. Die sind eigentlich gar nicht so schlecht. Für ersteres wir sehen, ob es sich um eine Instanz einer Klasse-Vorlage ist, in dem eine der Arten ist _:

template <typename T> 
struct is_lambda_expr : std::false_type { }; 

template <template <typename...> class C, typename... Ts> 
struct is_lambda_expr<C<Ts...>> : contains_type<_, Ts...> { }; 

Und für apply_lambda, werden wir nur die _ mit dem angegebenen Typ ersetzen:

template <typename T, typename U> 
struct apply_lambda; 

template <template <typename...> class C, typename... Ts, typename U> 
struct apply_lambda<C<Ts...>, U> { 
    using type = typename C<std::conditional_t<std::is_same<Ts, _>::value, U, Ts>...>::type; 
}; 

Und das ist alles, was Sie eigentlich brauchen. Ich werde es ausdehnen, um arg_<N> als Übung für den Leser zu unterstützen.

0

Ja, ich hatte dieses Problem zu. Es brauchte ein paar Iterationen, um einen vernünftigen Weg zu finden. Um dies zu erreichen, müssen wir eine angemessene Darstellung dessen geben, was wir wollen und brauchen.Ich habe einige Aspekte von std::bind() geliehen, in dem ich die Vorlage, die ich binden möchte, und die Parameter, die ich an sie binden möchte, angeben möchte. Innerhalb dieses Typs sollte dann eine Vorlage vorhanden sein, mit der Sie eine Reihe von Typen übergeben können.

So ist unsere Schnittstelle wird wie folgt aussehen:

template <template <typename...> class OP, typename...Ts> 
struct tbind; 

Jetzt ist unsere Implementierung dieser Parameter haben und einen Container von Typen, die am Ende angewendet wird:

template <template <typename...> class OP, typename PARAMS, typename...Ts> 
struct tbind_impl; 

Unser Basisfall wird geben Sie uns einen Vorlagentyp, den ich ttype aufrufen werde, der eine Vorlage der enthaltenen Typen zurückgeben wird:

template <template <typename...> class OP, typename...Ss> 
struct tbind_impl<OP, std::tuple<Ss...>> 
{ 
    template<typename...Us> 
    using ttype = OP<Ss...>; 
}; 

Dann haben wir den Fall, dass die nächste Art in den Behälter bewegt und ttype zum ttype in der etwas einfacheren Basisfall beziehen sich mit:

template <template <typename...> class OP, typename T, typename...Ts, typename...Ss> 
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...> 
{ 
    template<typename...Us> 
    using ttype = typename tbind_impl< 
      OP 
     , std::tuple<Ss..., T> 
     , Ts... 
    >::template ttype<Us...>; 
}; 

Und schließlich brauchen wir eine remap der Vorlagen, die sein wird, bestanden ttype:

template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss> 
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...> 
{ 
    template<typename...Us> 
    using ttype = typename tbind_impl< 
      OP 
     , typename std::tuple< 
       Ss... 
      , typename std::tuple_element< 
        I 
       , typename std::tuple<Us...> 
       >::type 
      > 
     , Ts... 
    >::template ttype<Us...>; 

Jetzt, da Programmierer sind faul und wollen nicht std::integral_constant<size_t, N> für jeden Parameter geben wir einige Aliase neu zuzuordnen, geben Sie:

using t0 = std::integral_constant<size_t, 0>; 
using t1 = std::integral_constant<size_t, 1>; 
using t2 = std::integral_constant<size_t, 2>; 
... 

Oh, fast vergessen, die Umsetzung unserer Schnittstelle:

template <template <typename...> class OP, typename...Ts> 
struct tbind : detail::tbind_impl<OP, std::tuple<>, Ts...> 
{}; 

Beachten Sie, dass tbind_impl in einem detail Namensraum getätigt.

Und voila, tbind!

Leider gibt es einen Defekt vor der C++ 17. Wenn Sie tbind<parms>::ttype an eine Vorlage übergeben, die eine Vorlage mit einer bestimmten Anzahl von Parametern erwartet, erhalten Sie einen Fehler, da die Anzahl der Parameter nicht übereinstimmt (die spezifische Zahl stimmt nicht mit einer Zahl überein). Dies macht Dinge komplizierter, die ein zusätzliches Maß an Indirektion erfordern. :(

template <template <typename...> class OP, size_t N> 
struct any_to_specific; 

template <template <typename...> class OP> 
struct any_to_specific<OP, 1> 
{ 
    template <typename T0> 
    using ttype = OP<T0>; 
}; 

template <template <typename...> class OP> 
struct any_to_specific<OP, 2> 
{ 
    template <typename T0, typename T1> 
    using ttype = OP<T0, T1>; 
}; 
... 

dass Verwenden von tbind zu wickeln den Compiler zwingen, die Vorlage mit der spezifizierten Anzahl von Parametern zu erkennen

Beispiel Nutzung:..

static_assert(!tbind<std::is_same, float, t0>::ttype<int>::value, "failed"); 

static_assert(tbind<std::is_same, int , t0>::ttype<int>::value, "failed"); 

static_assert(!any_to_specific< 
     tbind<std::is_same, float, t0>::ttype 
    , 1 
>::ttype<int>::value, "failed"); 

static_assert(any_to_specific< 
     tbind<std::is_same, int , t0>::ttype 
    , 1 
>::ttype<int>::value, "failed"); 

All das gelingt