2014-10-31 2 views
7

Я не уверен, что название правильное, но вот моя проблема/вопрос:C++ метапрограммирование автоматического создания функции?

Я хотел бы использовать метапрограммирование для создания функций для определенного выражения. Например позволяет сказать, что у нас есть этот код:

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

и программист будет писать код ниже для того, чтобы создать функцию для выражения:

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

Возможно ли это?

спасибо.

+2

Возможные: Да. Полезности: Спорная. Хотя есть много людей, которые любят метапрограммирование шаблонов, есть люди, которые пытаются избежать этого. Проблема в том, что вы никогда не видите расширенную версию своих шаблонов, если ошибка компилятора не выплевывает вас. Особенно шаблоны выражений, которые решают ваш вопрос, печально известны созданием ужасного беспорядка нечитаемых сообщений об ошибках на, казалось бы, невиновном коде. Мой совет: если вы хотите генерировать код, будьте честны и пишите скрипт (python, perl, m4, ...). Таким образом, вы можете читать как скрипт, так и сгенерированный код. – cmaster

+0

как просто написать 'auto func = [] (auto x, auto y, auto z) {return x + y + z;};', за которым следует вызов 'func (1,2,3)'? – davidhigh

ответ

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

Тест:

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

Выход:

6 
21 
24 

DEMO 1


Полученный раствор может быть улучшена, так что внедрение новых функторы требует меньше усилий, и Децл arations себя более удобным для чтения, как показано ниже:

#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. В качестве дополнительной абстракции вы могли бы избежать жесткого кодирования элементов «плюс» и «минус» и разрешить любую двоичную функцию (которая является 'constexpr'). Этого можно избежать в основном идентичном коде (поскольку они отличаются только '+' и '*'). – davidhigh

+0

@ davidhigh это то, что я сделал в [DEMO 2] (http://coliru.stacked-crooked.com/a/c4ee9c7ecbf29a75), где общий код был перемещен в класс 'binary_expression' –

+0

@ Eldrad: вы наверняка хотите использовать это для других типов, чем 'int', я думаю? Потому что вы знаете это, например., 'wrap <плюс <плюс <значение, значение>, умножить <значение, константа >>> :: func (a, 5, 2)' эквивалентно '(a + 5) + (2 * 6)', в то время как последнее, очевидно, намного проще писать ... – davidhigh

2

Абсолютно. Они называются «шаблонами выражений», и вы можете найти SO highlights here.

Я работал над системой POOMA для параллельного программирования еще в конце 90-х годов. Не уверен, что его обновили до современных стандартов, но я вижу, что он по-прежнему доступен онлайн here. Основой POOMA был «механизм шаблонов выражений» под названием PETE, который можно было бы перераспределить для других механизмов оценки. PETE описывается here. Вся эта работа будет намного проще с C++ 11, и я уверен, что там есть аналогичные усилия, которые используют эти новые возможности.

+2

Не могли бы вы дать краткий пример кода, основанный на том, что опубликовал OP, пожалуйста? –

+0

Я немного позабочусь - работаю прямо сейчас. :) – sfjac

Смежные вопросы