如何`std :: bind()`标准库算法?

Die*_*ühl 11 c++ c++11 c++14 c++17

我的问题的简短版本是:我如何使用std::bind()标准库算法之类的东西?

由于短版本有点缺乏细节,这里有一点解释:假设我有算法std::transform(),现在我想实现std::copy()(是的,我意识到std::copy()标准C++库中有).由于我很懒惰,我显然想要使用现有的实现std::transform().当然,我可以这样做:

struct identity {
    template <typename T>
    auto operator()(T&& value) const -> T&& { return std::forward<T>(value); }
};  
template <typename InIt, typename OutIt>
auto copy(InIt begin, InIt end, OutIt to) -> OutIt {
    return std::transform(begin, end, to, identity());
}
Run Code Online (Sandbox Code Playgroud)

不知何故,这种实现有点像一种算法的配置.例如,似乎std::bind()应该能够完成这项工作,但只是使用std::bind()不起作用:

namespace P = std::placeholders;
auto copy = std::bind(std::transform, P::_1, P::_2, P::_3, identity());
Run Code Online (Sandbox Code Playgroud)

问题是编译器无法仅从算法中确定适当的模板参数,如果存在&或不存在则无关紧要.有没有什么可以像使用std::bind()工作一样的方法?由于这是期待,我很满意一个解决方案,它已经提出了包含在C++标准中的任何内容.另外,为了摆脱我的懒惰,我很乐意在前面做一些工作,以便以后更容易使用.以这种方式思考:在我作为库实现者的角色中,我会把事情放在一起,这样每个库用户都可以变得懒惰:我是一个忙碌的实现者,但是一个懒惰的用户.

如果您想要一个现成的试验台:这是一个完整的程序.

#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>

using namespace std::placeholders;

struct identity {
    template <typename T>
    T&& operator()(T&& value) const { return std::forward<T>(value); }
};


int main()
{
    std::vector<int> source{ 0, 1, 2, 3, 4, 5, 6 };
    std::vector<int> target;

#ifdef WORKS
    std::transform(source.begin(), source.end(), std::back_inserter(target),
                   identity());
#else
    // the next line doesn't work and needs to be replaced by some magic
    auto copy = std::bind(&std::transform, _1, _2, _3, identity());
    copy(source.begin(), source.end(), std::back_inserter(target));
#endif
    std::copy(target.begin(), target.end(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n";
}
Run Code Online (Sandbox Code Playgroud)

Die*_*ühl 10

当尝试std::bind()重载函数时,编译器无法确定要使用哪个重载:在bind()评估-expression时,函数参数是未知的,即,重载决策无法决定选择哪个重载.在C++ [还没有?]中没有直接的方法将重载集视为一个对象.函数模板只是生成一个重载集,每个可能的实例化都有一个重载.也就是说,不能使用std::bind()任何标准C++库算法的整个问题围绕着标准库算法是函数模板的事实.

std::bind()算法具有相同效果的一种方法是使用C++ 14 通用lambdas来进行绑定,例如:

auto copy = [](auto&&... args){
    return std::transform(std::forward<decltype(args)>(args)..., identity());
};
Run Code Online (Sandbox Code Playgroud)

虽然这有效,但它实际上相当于功能模板的精巧实现,而不是配置现有功能.但是,使用通用lambdas在合适的标准库命名空间中创建主要功能对象可以使实际的底层功能对象容易获得,例如:

namespace nstd {
    auto const transform = [](auto&&... args){
        return std::transform(std::forward<decltype(args)>(args...));
    };
}
Run Code Online (Sandbox Code Playgroud)

现在,实现transform()它的方法实际上很容易用于std::bind()构建copy():

auto copy = std::bind(nstd::transform, P::_1, P::_2, P::_3, identity());
Run Code Online (Sandbox Code Playgroud)

尽管普通lambda的外观和使用,值得指出的是,仅使用C++ 11可用的功能创建相应的函数对象实际上需要大致相同的工作:

struct transform_t {
    template <typename... Args>
    auto operator()(Args&&... args) const
        -> decltype(std::transform(std::forward<decltype(args)>(args)...)) {
        return std::transform(std::forward<decltype(args)>(args)...);
    }
};
constexpr transform_t transform{};
Run Code Online (Sandbox Code Playgroud)

是的,这是更多的打字,但它只是使用通用lambda的一个合理的小常数因素,即,如果使用通用lambda的对象也是C++ 11版本.

当然,一旦我们有算法的函数对象,实际上甚至std::bind()不需要它们可能是很好的,因为我们需要提到所有未绑定的参数.在示例中它是currying(好吧,我认为currying仅适用于绑定第一个参数,但它是第一个参数还是最后一个参数似乎有点随机).如果我们有curry_first()curry_last()可以讨论第一个或最后一个论点怎么办?实现curry_last()也是微不足道的(为了简洁起见,我使用的是通用lambda,但可以使用与上面相同的重写来使其与C++ 11一起使用):

template <typename Fun, typename Bound>
auto curry_last(Fun&& fun, Bound&& bound) {
    return [fun = std::forward<Fun>(fun),
            bound = std::forward<Bound>(bound)](auto&&... args){
        return fun(std::forward<decltype(args)>(args)..., bound);
    };
}
Run Code Online (Sandbox Code Playgroud)

现在,假设它们curry_last()位于同一名称空间中,nstd::transform或者identity()定义为copy():

auto const copy = curry_last(nstd::transform, identity());
Run Code Online (Sandbox Code Playgroud)

好吧,也许这个问题没有给我任何帮助,但也许我会得到一些支持,将我们的标准库算法转换为函数对象,并可能添加一些很酷的方法来创建所述算法的绑定版本.我认为这种方法比这个领域的一些提案更安全(虽然上述形式可能不完整).