![](/img/trans.png)
[英]How to use std::bind with the standard library and save the return type?
[英]How to `std::bind()` a standard library algorithm?
我的問題的簡短版本是:我如何使用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 泛型lambdas來進行綁定,例如:
auto copy = [](auto&&... args){
return std::transform(std::forward<decltype(args)>(args)..., identity());
};
雖然這有效,但它實際上相當於功能模板的精巧實現,而不是配置現有功能。 但是,使用通用lambdas在合適的標准庫命名空間中創建主要功能對象可以使實際的底層功能對象容易獲得,例如:
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()
它們可能是很好的,因為我們需要提到所有未綁定的參數。 在示例中它是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);
};
}
現在,假設curry_last()
與nstd::transform
或identity()
位於同一名稱空間中, copy()
的定義可能變為:
auto const copy = curry_last(nstd::transform, identity());
好吧,也許這個問題沒有給我任何幫助,但也許我會得到一些支持,將我們的標准庫算法轉換為函數對象,並可能添加一些很酷的方法來創建所述算法的綁定版本。 我認為這種方法比這個領域的一些提案更安全(雖然上述形式可能不完整)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.