C ++ metaprogramming automatic function creation?

I'm not sure if the title is correct, but here is my problem / question:

I would like to use metaprogramming to create functions for a specific expression. For example, let's say we have this code:

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

and the programmer will write the following code to create a function for the expression:

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

Is it possible?

Thanks.

+7
c ++ templates template-meta-programming
source share
2 answers
 #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, "!"); } 

Output:

 6 21 24 

Demo 1


The above solution can be improved, so introducing new functors requires less effort, and the declarations themselves are more readable, for example, below:

 #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

+7
source share

That's right. These are called "expression patterns" and you can find SO highlights here .

I worked on a POOMA system for parallel programming back in the late 90s. Not sure if it has been updated to modern standards, but I see that it is still available online here . The basis of POOMA was an "expression template engine" called PETE, which could be redistributed to other evaluation mechanisms. PETE is described here . All this work will be much easier with C ++ 11, and I am sure that there are similar efforts that take advantage of these new features.

+2
source share

All Articles