gpt4 book ai didi

c++ - `std::bind()` 标准库算法如何实现?

转载 作者:可可西里 更新时间:2023-11-01 15:17:42 24 4
gpt4 key购买 nike

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

由于简短版本有点缺乏细节,这里有一点解释:假设我有算法 std::transform() 现在我想实现 std::copy()(是的,我知道标准 C++ 库中有 std::copy())。由于我极其懒惰,我显然想使用 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";
}

最佳答案

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

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

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 的一个合理的小常数因子,即,如果对象使用通用 lambda,C++11 版本也是。

当然,一旦我们有了算法的函数对象,实际上什至不必 std::bind() 它们可能会很整洁,因为我们需要提及所有未绑定(bind)的参数.在示例中它是 currying (好吧,我认为柯里化(Currying)只适用于绑定(bind)第一个参数,但它是第一个还是最后一个参数似乎有点随机)。如果我们有 curry_first()curry_last() 来柯里化(Currying)第一个或最后一个参数会怎么样? 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::transformidentity() 的同一命名空间中 copy() 可以变成:

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

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

关于c++ - `std::bind()` 标准库算法如何实现?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27694035/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com