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


我的问题的简短版本是这样的:我怎样才能使用类似的东西 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());
}

不知怎的,这种实现有点像感觉 组态 一种算法。例如,似乎好像 std::bind() 应该能够完成这项工作,但只需使用 std::bind() 不起作用:

namespace P = std::placeholders;
auto copy = std::bind(std::transform, P::_1, P::_2, P::_3, identity());

问题是编译器无法仅从算法中确定适当的模板参数,如果存在,则无关紧要 & 或不。是否存在可以使用的方法 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";
}

1814
2017-12-29 18:25


起源

@πάνταῥεῖ:嘿 - 这是一个真正的问题......! (当然,双重目标确实起作用) - Dietmar Kühl
标签组合是什么让我怀疑;-) ......(这当然是个好问题) - πάντα ῥεῖ
通用lambda几乎是我能想到的唯一的东西,但我认为这是不够的 bind-喜欢... - T.C.
您一定听说过C ++ 14技术通过通用lambda传递重载集。这种方法有什么不对或不足? - dyp
@dyp:你想用一个 宏?啧啧......!但是,是的,这更接近我的想法。此外,我目前正试图获得类似的工作,似乎我正在使用的实现并不完全满意转发占位符。 - Dietmar Kühl


答案:


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

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

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

虽然这有效,但它实际上相当于功能模板的精巧实现,而不是配置现有功能。但是,使用通用的Lambda表达式来创建一个合适的标准库命名空间中的主要作用对象可以使实际的底层函数对象一应俱全,例如:

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

现在,采用实施的方法 transform() 它实际上是微不足道的 std::bind() 建立 copy()

auto copy = std::bind(nstd::transform, P::_1, P::_2, P::_3, identity());

尽管看上去和使用的普通的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{};

是的,这是更多的输入,但如果使用普通的lambda表达式的C ++ 11版本的对象,也它只是一个合理的小常数因子在使用通用的Lambda表达式,即的。

当然,一旦我们有算法的函数对象,它实际上甚至不必必须 std::bind() 因为我们需要提及所有未绑定的参数。在示例中它是 柯里 (好吧,我认为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);
    };
}

现在,假设 curry_last() 生活在同一个命名空间中 nstd::transform 要么 identity() 的定义 copy() 可能成为:

auto const copy = curry_last(nstd::transform, identity());

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


10
2017-12-29 20:13



非标准扩展(甚至是好的扩展)的存在并不意味着它在C ++中是必需的! (一个 constexpr 标准不要求在无状态lambda上创建/移动/复制构造函数。如果我没记错的话,它都不被排除在外) - Yakk - Adam Nevraumont
@Yakk:是的,我被一个扩展程序欺骗了(实际上只有gcc而不是clang)。 - Dietmar Kühl
@Yakk lambdas可能不会出现在常量表达式中,我认为排除“允许但不可移植” - dyp
@dyp:是的,你是对的。我改变了它们 const。我不太喜欢它,但是:在正常初始化之前无法构造,这意味着即使使用C ++ 14,也需要使用使用适当函数对象而不是通用lambda的方法... - Dietmar Kühl
@Yakk Closure类型显然不是每个[expr.prim.lambda] / p3的文字类型,并且 constexpr 对象声明需要文字类型([dcl.constexpr] / p9)。 - T.C.