2014-10-31 5 views
7

Non sono sicuro che il titolo sia corretto ma questo è il mio problema/domanda:C++ metaprogrammazione creazione automatica delle funzioni?

Vorrei utilizzare la metaprogrammazione per creare funzioni per un'espressione specifica. Per esempio permette di dire che abbiamo di questo codice:

template<typename T1, typename T2> 
struct plus{ 
    T1 func(T1 in1, T2 in2){ return in1 + in2; } 
}; 

template<typename T1, typename T2, typename T3, typename expr> 
struct wrap{ 

    /* contain a func that can evaluate the expr */ 
}; 

e il programmatore scrivere il codice soffietto al fine di creare una funzione per l'espressione:

wrap<int,int,int,plus<plus<int,int>,int> >::func(1,2,3); /*result should be 6*/ 

è possibile?

Grazie.

+2

possibile: Sì. Consigliabile: contestabile. Mentre ci sono molte persone là fuori che amano il metaprogrammazione del modello, ci sono anche persone che cercano di evitarlo. Il problema è che non vedi mai la versione espansa dei tuoi modelli a meno che un errore del compilatore non sputi a te. Soprattutto i modelli di espressione che risolvono la tua domanda sono noti per produrre un orribile pasticcio di messaggi di errore illeggibili su un codice apparentemente innocente. Il mio consiglio: se vuoi generare codice, sii onesto e scrivi uno script (python, perl, m4, ...). In questo modo puoi leggere sia lo script sia il codice generato. – cmaster

+0

cosa dire semplicemente scrivendo 'auto func = [] (auto x, auto y, auto z) {return x + y + z;};', seguito da una chiamata 'func (1,2,3)'? – davidhigh

risposta

7
#include <utility> 
#include <tuple> 
#include <cstddef> 

struct arg 
{ 
    template <typename Arg1> 
    static constexpr decltype(auto) apply(Arg1&& arg1) 
    { 
     return std::forward<Arg1>(arg1); 
    } 

    static constexpr std::size_t arity = 1; 
}; 

template <typename Type, Type value> 
struct constant 
{  
    static constexpr decltype(auto) apply() 
    { 
     return value; 
    } 

    static constexpr std::size_t arity = 0; 
}; 

template <typename Lhs, typename Rhs> 
struct plus 
{ 
    template <typename... Args> 
    static constexpr decltype(auto) apply(Args&&... args) 
    { 
     return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...)); 
    } 

    template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2> 
    static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args) 
    { 
     return Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...) 
      + Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...); 
    } 

    static constexpr std::size_t arity = Lhs::arity + Rhs::arity; 
}; 

template <typename Lhs, typename Rhs> 
struct multiply 
{ 
    template <typename... Args> 
    static constexpr decltype(auto) apply(Args&&... args) 
    { 
     return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...)); 
    } 

    template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2> 
    static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args) 
    { 
     return Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...) 
      * Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...); 
    } 

    static constexpr std::size_t arity = Lhs::arity + Rhs::arity; 
}; 

Test:

int main() 
{ 
    // (1 + 2) + 3 = 6 
    std::cout << plus<plus<arg, arg>, arg>::apply(1, 2, 3) << std::endl; 

    // (a + 5) + (2 * 6) = 9 + 12 = 21 
    int a = 4; 
    std::cout << plus<plus<arg, arg>, multiply<arg, constant<int, 6>>>::apply(a, 5, 2) << std::endl; 

    // ((1 * 2) * 3) * 4 = 24 
    std::cout << multiply<multiply<multiply<arg, arg>, arg>, arg>::apply(1, 2, 3, 4) << std::endl; 

    // 2 + (4 * 5) = 22 
    static_assert(plus<arg, multiply<arg, arg>>::apply(2, 4, 5) == 22, "!"); 
} 

uscita:

6 
21 
24 

DEMO 1


Questa soluzione può essere migliorata in modo che l'introduzione di nuove functors richiede meno sforzo, e decl mentari stessi sono più leggibile, come di seguito:

#include <iostream> 
#include <utility> 
#include <tuple> 
#include <cstddef> 

template <std::size_t Arity> 
struct expression 
{  
    static constexpr std::size_t arity = Arity; 
}; 

template <typename Expr, typename Rhs> 
struct unary_expression : expression<Rhs::arity> 
{  
    template <typename... Args> 
    static constexpr decltype(auto) apply(Args&&... args) 
    { 
     static_assert(sizeof...(Args) == unary_expression::arity, "Wrong number of operands!"); 
     return Expr::eval(Rhs::apply(std::forward<Args>(args)...)); 
    } 
}; 

template <typename Expr, typename Lhs, typename Rhs> 
struct binary_expression : expression<Lhs::arity + Rhs::arity> 
{ 
    template <typename... Args> 
    static constexpr decltype(auto) apply(Args&&... args) 
    { 
     static_assert(sizeof...(Args) == binary_expression::arity, "Wrong number of operands!"); 
     return _apply(std::make_index_sequence<Lhs::arity>{}, std::make_index_sequence<Rhs::arity>{}, std::tuple<Args&&...>(std::forward<Args>(args)...)); 
    } 

    template <typename Tuple, std::size_t... Arity1, std::size_t... Arity2> 
    static constexpr decltype(auto) _apply(std::index_sequence<Arity1...>, std::index_sequence<Arity2...>, Tuple&& args) 
    { 
     return Expr::eval(Lhs::apply(static_cast<typename std::tuple_element<Arity1, Tuple>::type>(std::get<Arity1>(args))...), 
          Rhs::apply(static_cast<typename std::tuple_element<Lhs::arity + Arity2, Tuple>::type>(std::get<Lhs::arity + Arity2>(args))...)); 
    } 
}; 

struct arg : expression<1> 
{ 
    template <typename Arg1> 
    static constexpr decltype(auto) apply(Arg1&& arg1) 
    { 
     return std::forward<Arg1>(arg1); 
    } 
}; 

template <typename Type, Type value> 
struct constant : expression<0> 
{  
    static constexpr decltype(auto) apply() 
    { 
     return value; 
    } 
}; 

template <typename Rhs> 
struct negate : unary_expression<negate<Rhs>, Rhs> 
{ 
    template <typename Arg1> 
    static constexpr decltype(auto) eval(Arg1&& arg1) 
    { 
     return -std::forward<Arg1>(arg1); 
    } 
}; 

template <typename Lhs, typename Rhs> 
struct plus : binary_expression<plus<Lhs, Rhs>, Lhs, Rhs> 
{ 
    template <typename Arg1, typename Arg2> 
    static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2) 
    { 
     return std::forward<Arg1>(arg1) + std::forward<Arg2>(arg2); 
    } 
}; 

template <typename Lhs, typename Rhs> 
struct minus : binary_expression<minus<Lhs, Rhs>, Lhs, Rhs> 
{ 
    template <typename Arg1, typename Arg2> 
    static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2) 
    { 
     return std::forward<Arg1>(arg1) - std::forward<Arg2>(arg2); 
    } 
}; 

template <typename Lhs, typename Rhs> 
struct multiply : binary_expression<multiply<Lhs, Rhs>, Lhs, Rhs> 
{ 
    template <typename Arg1, typename Arg2> 
    static constexpr decltype(auto) eval(Arg1&& arg1, Arg2&& arg2) 
    { 
     return std::forward<Arg1>(arg1) * std::forward<Arg2>(arg2); 
    } 
}; 

int main() 
{  
    // (1 + 2) + 3 = 6 
    std::cout << plus<plus<arg, arg>, arg>::apply(1, 2, 3) << std::endl; 

    // ((a + 5) + (2 * 6)) - 5 = 16 
    int a = 4; 
    std::cout << minus<plus<plus<arg, arg>, multiply<arg, constant<int, 6>>>, constant<int, 5>>::apply(a, 5, 2) << std::endl; 

    // ((1 * 2) * 3) * 4 = 24 
    std::cout << multiply<multiply<multiply<arg, arg>, arg>, arg>::apply(1, 2, 3, 4) << std::endl; 

    // -((3 * 4) + (5 - 6)) = -11 
    static_assert(negate<plus<multiply<arg, arg>, minus<arg, arg>>>::apply(3, 4, 5, 6) == -11, "!"); 
} 

DEMO 2

+0

+1. Come un'ulteriore astrazione, è possibile evitare di codificare a fondo le risorse 'plus' e' minus' e consentire qualsiasi funzione binaria (che è 'constexpr'). Questo è possibile evitare il codice per lo più identico (poiché differiscono solo da '+' e '*'). – davidhigh

+0

@davidhigh questo è quello che ho fatto in [DEMO 2] (http://coliru.stacked-crooked.com/a/c4ee9c7ecbf29a75), in cui il codice comune è stato spostato nella classe 'binary_expression' –

+0

@Eldrad: si desidera sicuramente utilizzare questo per altri tipi di 'int', immagino? Perché tu lo sai, ad es., 'wrap , moltiplica >>> :: func (a, 5, 2)' è equivalente a '(a + 5) + (2 * 6)', considerando che quest'ultimo è ovviamente molto più facile da scrivere ... – davidhigh

2

Assolutamente. Questi sono chiamati "modelli di espressione" e puoi trovare i punti salienti di SO here.

Ho lavorato al sistema POOMA per la programmazione parallela alla fine degli anni '90. Non sono sicuro se è stato aggiornato agli standard moderni, ma vedo che è ancora disponibile online here. Il sottostante POOMA era un "Expression Template Engine" chiamato PETE che poteva essere riproposto per altri motori di valutazione. PETE è descritto here. Tutto questo lavoro sarebbe molto più semplice con C++ 11 e sono sicuro che ci sono sforzi simili là fuori che usano queste nuove funzionalità.

+2

Potrebbe fornire un codice di esempio conciso, in base a ciò che l'OP ha pubblicato, per favore? –

+0

Prenderò un'occhiata dopo un po ', lavorando proprio ora. :) – sfjac