2014-11-25 23 views
10

Questa è più di una domanda concettuale. Sto cercando di trovare il modo più semplice di convertire un modello a due argomenti (gli argomenti in base ai tipi) in un modello ad un argomento. I.e, vincolando uno dei tipi.Currying per modelli in C++ metaprogramming

Questo sarebbe l'equivalente meta-programmazione di bind in boost/std. Nell'esempio è incluso un possibile caso d'uso, ovvero lo std::is_same come argomento del modello per un modello che accetta un argomento modello di template ad un argomento (std::is_same come modello a due argomenti), ad esempio TypeList::FindIf. Lo TypeList non è completamente implementato qui, né lo è FindIf, ma si ottiene l'idea. Ci vuole un "predicato unario" e restituisce il tipo per il quale quel predicato è vero o void se non è di questo tipo.

Ho 2 varianti di lavoro, ma il primo non è un one-liner e il 2 utilizza un aggeggio piuttosto dettagliato BindFirst, che non funzionerebbe per argomenti modello non di tipo. C'è un modo semplice per scrivere un one-liner così? Credo che la procedura che sto cercando si chiami 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; 
} 

Clicca here per il codice in Godbolt compilatore on-line.

+0

Dal momento che ritengo che la risposta sia "no, non esiste un modo più semplice", accetterò ovviamente qualsiasi risposta che fornisca alcune informazioni utili (come ad esempio i piani per includere un tale ng nello standard). – haelix

+1

Le risposte alla domanda ["Come posso seguire i parametri del modello di modello variadic?"] (Http://stackoverflow.com/q/21406726/3043539) può essere utile. – Constructor

+1

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

risposta

3

Penso che il modo tipico per farlo sia mantenere tutto nel mondo dei tipi. Non prendere modelli di modelli: sono disordinati. Scriviamo un metafunction nome ApplyAnInt che avrà una "classe metafunction" e applicare int ad esso:

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

Dove una semplice classe metafunction potrebbe essere solo controllando se il tipo di dato è un int:

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

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

Ora l'obiettivo è quello di sostenere:

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

possiamo farlo. Stiamo per chiamare i tipi che contengono _ "espressioni lambda", e scrivere un metafunction chiamato lambda che sia in avanti una classe metafunction che non è un'espressione lambda, o produrre una nuova metafunction se è:

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; 

Così abbiamo aggiornare la nostra metafunction originale:

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

Ora che lascia due cose: abbiamo bisogno is_lambda_expr e apply_lambda. Quelli in realtà non sono poi così male. Per i primi, vedremo se si tratta di un'istanza di una classe template in cui uno dei tipi è _:

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

E per apply_lambda, abbiamo appena sostituirà il _ con il tipo data:

E questo è tutto ciò che serve in realtà. Lascerò estendere questo supporto per supportare arg_<N> come esercizio per il lettore.

0

Sì, ho avuto questo problema a. Ci sono volute alcune iterazioni per trovare un modo decente per farlo. Fondamentalmente, per fare ciò, dobbiamo specificare una rappresentazione ragionevole di ciò che vogliamo e di cui abbiamo bisogno.Ho preso in prestito alcuni aspetti da std::bind() in cui voglio specificare il modello che desidero legare e i parametri che voglio associare ad esso. Quindi, all'interno di quel tipo, dovrebbe esserci un modello che ti permetterà di superare un insieme di tipi.

Così la nostra interfaccia sarà simile a questa:

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

Ora la nostra applicazione avrà quei parametri, più un contenitore di tipi che verranno applicate al termine:

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

Il nostro caso base sarà darci un modello, che chiamerò ttype, che restituirà un modello dei tipi contenuti:

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

allora abbiamo il caso di spostare il prossimo tipo nel contenitore e dover ttype si riferiscono alla ttype nel caso base leggermente più semplice:

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

E, infine, abbiamo bisogno di una rimappatura dei modelli che saranno passati a 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...>; 

Ora, dal momento che i programmatori sono pigri e non vogliono digitare std::integral_constant<size_t, N> per ogni parametro per rimappare, specifichiamo alcuni alias:

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

Ah, quasi dimenticavo l'attuazione della nostra interfaccia:

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

Nota che tbind_impl è stata posta in uno spazio dei nomi detail.

E voilà, tbind!

Sfortunatamente, c'è un difetto prima di C++ 17. Se si passa tbind<parms>::ttype a un modello che si aspetta un modello con un numero particolare di parametri, si otterrà un errore in quanto il numero di parametri non corrisponde (il numero specifico non corrisponde a nessun numero). Ciò complica le cose richiedendo un ulteriore livello di riferimento indiretto. :(

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

Utilizzando che per avvolgere tbind costringerà il compilatore di riconoscere il modello avente il numero specificato di parametri

Esempio di utilizzo:..

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

Tutto ciò riesce