【问题标题】:C++ metaprogramming automatic function creation?C++元编程自动函数创建?
【发布时间】:2014-10-31 18:56:16
【问题描述】:

我不确定标题是否正确,但这是我的问题/疑问:

我想使用元编程来为特定表达式创建函数。例如,假设我们有以下代码:

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

这可能吗?

谢谢。

【问题讨论】:

  • 可能:是的。可取的:有争议的。虽然有很多人喜欢模板元编程,但也有人试图避免它。问题是,除非编译器错误将其吐出,否则您永远看不到模板的扩展版本。尤其是解决您的问题的表达式模板因在看似无辜的代码上产生大量无法阅读的错误消息而臭名昭著。我的建议:如果您想生成代码,请诚实对待它并编写脚本(python、perl、m4、...)。这样您就可以同时阅读脚本和生成的代码。
  • 简单写auto func= [](auto x,auto y,auto z) {return x+y+z;};,然后调用func(1,2,3)怎么样?

标签: c++ templates template-meta-programming


【解决方案1】:
#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


上面的解决方案可以改进,以便引入新的函子需要更少的努力,并且声明本身更具可读性,如下所示:

#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

【讨论】:

  • +1。作为进一步的抽象,您可以避免对plusminus 进行硬编码,并允许使用任何二进制函数(即constexpr)。这可以避免大部分相同的代码(因为它们的区别仅在于 +*)。
  • @davidhigh 这是我在DEMO 2 中所做的,其中公共代码已移至binary_expression
  • @Eldrad:我猜你肯定想将它用于int 以外的其他类型?因为你知道,例如,wrap&lt;plus&lt;plus&lt;value, value&gt;, multiply&lt;value, constant&lt;int, 6&gt;&gt;&gt;&gt;::func(a, 5, 2) 等价于 (a + 5) + (2 * 6),而后者显然更容易编写...
  • @davidhigh 你不能在编译时通过元编程技术扩展(a + 5) + (2 * 6)[](auto x,auto y,auto z) {return x+y+z;},可能将它们作为简单的模板参数进行操作。我认为这就是问题的全部意义所在。
  • 感谢您的信息。但是随后,实现表达式的单个constexpr 将代替几页代码(或者如果表达式变得过于复杂,则可能不止一个)。我认为不使用它没有任何好处……至少对于基本类型而言没有。有吗?
【解决方案2】:

当然。这些被称为“表达式模板”,您可以找到 SO 亮点 here

早在 90 年代后期,我就曾在 POOMA 系统上工作以进行并行编程。不确定它是否已更新为现代标准,但我看到它仍然可以在线使用here。底层 POOMA 是一个名为 PETE 的“表达式模板引擎”,可以重新用于其他评估引擎。皮特被描述为here。使用 C++ 11,所有这些工作都会变得更加简单,而且我相信会有类似的努力使用这些新功能。

【讨论】:

  • 能否根据OP发布的内容给出一个简洁的示例代码?
  • 我会稍微看一下 - 现在正在工作。 :)
猜你喜欢
  • 2016-02-22
  • 2013-08-04
  • 1970-01-01
  • 2011-02-23
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多