2014-10-31 20 views
7

Ich bin mir nicht sicher, ob der Titel korrekt ist, aber hier ist mein Problem/Frage:C++ Metaprogrammierung automatische Funktionserstellung?

Ich möchte metaprogramming verwenden, um Funktionen für einen bestimmten Ausdruck zu erstellen. Zum Beispiel kann sagen, dass wir diesen Code haben:

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 */ 
}; 

und der Programmierer schreibt den Code unten, um eine Funktion für einen Ausdruck zu erstellen:

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

Ist das möglich?

Vielen Dank.

+2

möglich: Ja. Ratsam: Streitbar. Während es viele Leute gibt, die Template-Metaprogrammierung lieben, gibt es auch Leute, die versuchen, es zu vermeiden. Das Problem ist, dass Sie die erweiterte Version Ihrer Vorlagen nie sehen, es sei denn, ein Compiler-Fehler spuckt es bei Ihnen aus. Vor allem die Expression-Templates, die deine Frage lösen, sind berüchtigt dafür, dass sie auf unschuldigem Code eine schreckliche Menge unleserlicher Fehlermeldungen produzieren. Mein Tipp: Wenn Sie Code generieren wollen, seien Sie ehrlich und schreiben Sie ein Skript (Python, Perl, M4, ...). Auf diese Weise können Sie sowohl Skript als auch generierten Code lesen. – cmaster

+0

was ist mit einfach schreiben 'auto func = [] (auto x, auto y, auto z) {return x + y + z;};', gefolgt von einem Aufruf 'func (1,2,3)'? – davidhigh

Antwort

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, "!"); 
} 

Ausgang:

6 
21 
24 

DEMO 1


Die obige Lösung verbessert werden kann, so dass neue functors Einführung weniger Aufwand erfordert, und die dekl arations selbst sind besser lesbar, wie unten:

#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. Als eine weitere Abstraktion könnte man vermeiden, die "Plus" - und "Minus" -Sachen hart zu codieren und jede binäre Funktion zuzulassen (was "constexpr" ist). Dadurch könnten Sie den meist identischen Code umgehen (da sie sich nur durch '+' und '*' unterscheiden). – davidhigh

+0

@davidhigh das ist, was ich in [DEMO 2] (http://coliru.stacked-crooked.com/a/c4ee9c7ecbf29a75) gemacht habe, wo gemeinsamer Code in 'binary_expression' Klasse –

+0

@Eldrad verschoben wurde: Sie sicherlich verwenden möchten das für andere Typen als 'int', schätze ich? Weil Sie das wissen, z.B., , multiplizieren >>> :: func (a, 5, 2) 'ist äquivalent zu' (a + 5) + (2 * 6) ', während letzteres ist offensichtlich viel einfacher zu schreiben ... – davidhigh

2

Absolut. Diese werden "Expression Templates" genannt und Sie finden die SO Highlights here.

Ich arbeitete in den späten 90er Jahren am POOMA-System für die parallele Programmierung. Nicht sicher, ob es zu den modernen Standards aktualisiert worden ist, aber ich sehe, dass es noch online verfügbar ist here. Der zugrunde liegende POOMA war eine "Expression Template Engine" namens PETE, die für andere Evaluierungs-Engines verwendet werden konnte. PETE wird here beschrieben. All diese Arbeit wäre mit C++ 11 viel einfacher und ich bin sicher, es gibt ähnliche Bemühungen, die diese neueren Fähigkeiten nutzen.

+2

Könnten Sie bitte einen kurzen Beispielcode geben, basierend auf den Angaben des OP? –

+0

Ich werde mich um etwas kümmern - arbeite gerade. :) – sfjac