如何在c ++ 17中定义函数组合?

时间:2018-04-24 22:06:57

标签: c++ c++17 auto

我想计算函数组成 - f(g(param))。这是我试过的:

auto fComposition(auto&& f, auto&& g, auto&&... params)
{
    /* some stuff */
    auto result = std::forward<decltype(f)>(f)(
                      std::forward<decltype(g)>(g)(
                          std::forward<decltype(params)>(param)
                      )
                  );
    /* other stuff */
    return result;
};

使用

进行编译
g++ -std=c++17 src.cpp

基本测试

#include <random>
#include <math.h>
int main()
{
    std::random_device rd;                                                                          
    std::mt19937 gen(rd());                                                                         
    std::uniform_real_distribution<double> distr(-1.0, 1.0);

    auto xx = fComposition(round, distr, gen);
    return 0;
}

我收到的消息是它无法识别第一个函数的类型。

2 个答案:

答案 0 :(得分:2)

顺便说一下,这真的是你的代码吗?你没有扩展params所以它不应该编译。

予。您定义组合的方式与简单调用无法区分:除了额外的字符输入外,您的fComposition(f, g, arg)f(g(arg))相同。真正的组合通常是一个组合器,它接受两个函数并返回一个闭包,当在实际参数上调用时,它会连续应用它们。类似的东西:

template<class F, class G> auto comp(F f, G g) {
    return [f, g](auto &&... args) {
        return f(g(std::forward<decltype(args)>(args)...));
    };
}

(注意 - 值绑定。在C ++ 17中,它们比二十年前更先进。:)你可以按品尝添加std::movestd::forward。)

这样你就可以组成两个函数:

auto fg = comp(f, g);

然后在参数上调用结果:

auto x = fg(arg1, arg2);

II。但实际上,为什么要用两个操作数限制自己呢?在Haskell中,(.)是一个单独的二元函数。在C ++中,我们可以拥有一整套重载树:

template<class Root, class... Branches> auto comp(Root &&root, Branches &&... branches) {
     return [root, branches...](auto &&...args) {
         return root(branches(std::forward<decltype(args)>(args)...)...);
     };
}

现在您可以将任何AST封装在一个可调用的中:

int f(int x, int y) { return x + y; }
int g(int x) { return x * 19; }
int h(int x) { return x + 2; }

#include <iostream>
int main() {
    auto fgh = comp(f, g, h);
    std::cout << fgh(2) << '\n';
}

类似的技术是我知道在11标准之前使用C ++进行匿名闭包的唯一方法。

III。但等等,是否有图书馆解决方案?事实上,是的。来自std::bind's description

  

如果存储的参数arg是类型T,std::is_bind_expression<T>::value == true(例如,另一个绑定表达式直接传递给bind的初始调用),则bind执行函数组合:而不是传递函数对象bind子表达式将返回,子表达式被急切调用,其返回值被传递给外部的invokable对象。如果bind子表达式具有任何占位符参数,则它们将与外部绑定共享(从u1, u2, ...中选择)。具体而言,上面vn调用中的参数std::invokearg(std::forward<Uj>(uj)...),同一调用中的Vn类型为std::result_of_t<T cv &(Uj&&...)>&&(cv资格与此相同g)。

对不起,此刻此处没有示例。 &GT; _&LT;

P.S。是的,std::round是一个重载函数,所以你应该对它进行类型转换,以指定你需要组成哪个确切的重载。

答案 1 :(得分:0)

random的包含cmathlibstdc++中的round也定义了默认命名空间中的几个数学运算符(包括std)以及round {1}}命名空间。 (参见this answer的基本原理。)C ++的round有多个重载。因此,您有几个版本的round可用,并且您的函数不知道您要使用哪个round,因此会出现有关歧义的错误消息。正确的解决方案是消除你的意思static_cast<double(*)(double)>(round) 。您可以使用静态强制转换来执行此操作:

cmath

由于您无论如何都要经历麻烦,您也可以使用math.h标题代替std::round并使用display: block !important;代替。至少你知道它会在前面超载。