[英]C++11 - std::function, templates and function objects, weird issues
[英]c++11: How to write a wrapper function to make `std::function` objects
我試圖寫一個包裝器make_function
,它像std::make_pair
可以從合適的可調用對象中創建一個std::function
對象。
就像make_pair
一樣,對於函數指針foo
, auto f0 = make_function(foo);
創建具有正確類型簽名的std::function
函數對象f0
。 只是為了澄清,我不介意偶爾將類型參數提供給make_function
,以防很難(或不可能)完全從參數推導類型。
到目前為止,我想出的東西(下面的代碼)對lambda,某些函數指針和函子(我沒有考慮過volatile)可以很好地工作。 但是我無法將其用於std::bind
或std::bind<R>
結果。 在下面的代碼中
auto f2 = make_function(std::bind(foo,_1,_2,_3)); //not OK
使用gcc 4.8.1無法編譯/工作。 我猜我沒有正確捕獲bind
結果的operator()
,但是我不確定如何解決它。
感謝您提供有關如何解決此問題或在其他極端情況下進行改進的幫助。
我的問題是,當然,如何修復以下示例中的錯誤。
對於背景,我可以在以下問題中找到我使用此包裝器的情況之一: 如何使帶有function <>參數的C ++ 11函數自動接受lambda 。 如果您不同意使用std::function
或我的特定使用方式,請在該帖子中留下您的評論,並在此處討論技術問題。
-編輯-
從一些評論中,我了解到這是由於歧義問題( std::bind
結果的函數調用operator()的歧義)造成的。 正如@Mooing Duck的答案所指出的,解決方案是顯式地指定參數類型。 我已經更新了代碼,以結合@Mooing Duck的答案中的三個函數(類型參數稍有變化),以便make_function
包裝器現在可以像以前一樣處理/類型推斷明確的情況,並允許在存在時指定完整的類型簽名是模棱兩可的。
(我針對這些情況的原始代碼位於: https : //stackoverflow.com/a/21665705/683218 ,可以在以下網址進行測試: https : //ideone.com/UhAk91 ):
#include <functional>
#include <utility>
#include <iostream>
#include <functional>
using namespace std;
// For generic types that are functors, delegate to its 'operator()'
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) > {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
// for function pointers
template <typename ReturnType, typename... Args>
struct function_traits<ReturnType (*)(Args...)> {
enum { arity = sizeof...(Args) };
typedef function<ReturnType (Args...)> f_type;
};
template <typename L>
static typename function_traits<L>::f_type make_function(L l){
return (typename function_traits<L>::f_type)(l);
}
//handles bind & multiple function call operator()'s
template<typename ReturnType, typename... Args, class T>
auto make_function(T&& t)
-> std::function<decltype(ReturnType(t(std::declval<Args>()...)))(Args...)>
{return {std::forward<T>(t)};}
//handles explicit overloads
template<typename ReturnType, typename... Args>
auto make_function(ReturnType(*p)(Args...))
-> std::function<ReturnType(Args...)> {
return {p};
}
//handles explicit overloads
template<typename ReturnType, typename... Args, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...))
-> std::function<ReturnType(Args...)> {
return {p};
}
// testing
using namespace std::placeholders;
int foo(int x, int y, int z) { return x + y + z;}
int foo1(int x, int y, int z) { return x + y + z;}
float foo1(int x, int y, float z) { return x + y + z;}
int main () {
//unambuiguous
auto f0 = make_function(foo);
auto f1 = make_function([](int x, int y, int z) { return x + y + z;});
cout << make_function([](int x, int y, int z) { return x + y + z;})(1,2,3) << endl;
int first = 4;
auto lambda_state = [=](int y, int z) { return first + y + z;}; //lambda with states
cout << make_function(lambda_state)(1,2) << endl;
//ambuiguous cases
auto f2 = make_function<int,int,int,int>(std::bind(foo,_1,_2,_3)); //bind results has multiple operator() overloads
cout << f2(1,2,3) << endl;
auto f3 = make_function<int,int,int,int>(foo1); //overload1
auto f4 = make_function<float,int,int,float>(foo1); //overload2
return 0;
}
問題是您的代碼無法正確處理lambda,bind或functionoid,因此您的代碼假定所有這些都不帶參數。 要處理這些,您必須指定參數類型:
//plain function pointers
template<typename... Args, typename ReturnType>
auto make_function(ReturnType(*p)(Args...))
-> std::function<ReturnType(Args...)>
{return {p};}
//nonconst member function pointers
template<typename... Args, typename ReturnType, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...))
-> std::function<ReturnType(Args...)>
{return {p};}
//const member function pointers
template<typename... Args, typename ReturnType, typename ClassType>
auto make_function(ReturnType(ClassType::*p)(Args...) const)
-> std::function<ReturnType(Args...)>
{return {p};}
//qualified functionoids
template<typename FirstArg, typename... Args, class T>
auto make_function(T&& t)
-> std::function<decltype(t(std::declval<FirstArg>(), std::declval<Args>()...))(FirstArg, Args...)>
{return {std::forward<T>(t)};}
//unqualified functionoids try to deduce the signature of `T::operator()` and use that.
template<class T>
auto make_function(T&& t)
-> decltype(make_function(&std::remove_reference<T>::type::operator()))
{return {std::forward<T>(t)};}
變量:
int func(int x, int y, int z) { return x + y + z;}
int overloaded(char x, int y, int z) { return x + y + z;}
int overloaded(int x, int y, int z) { return x + y + z;}
struct functionoid {
int operator()(int x, int y, int z) { return x + y + z;}
};
struct functionoid_overload {
int operator()(int x, int y, int z) { return x + y + z;}
int operator()(char x, int y, int z) { return x + y + z;}
};
int first = 0;
auto lambda = [](int x, int y, int z) { return x + y + z;};
auto lambda_state = [=](int x, int y, int z) { return x + y + z + first;};
auto bound = std::bind(func,_1,_2,_3);
測試:
std::function<int(int,int,int)> f0 = make_function(func); assert(f0(1,2,3)==6);
std::function<int(char,int,int)> f1 = make_function<char,int,int>(overloaded); assert(f1(1,2,3)==6);
std::function<int(int,int,int)> f2 = make_function<int,int,int>(overloaded); assert(f2(1,2,3)==6);
std::function<int(int,int,int)> f3 = make_function(lambda); assert(f3(1,2,3)==6);
std::function<int(int,int,int)> f4 = make_function(lambda_state); assert(f4(1,2,3)==6);
std::function<int(int,int,int)> f5 = make_function<int,int,int>(bound); assert(f5(1,2,3)==6);
std::function<int(int,int,int)> f6 = make_function(functionoid{}); assert(f6(1,2,3)==6);
std::function<int(int,int,int)> f7 = make_function<int,int,int>(functionoid_overload{}); assert(f7(1,2,3)==6);
std::function<int(char,int,int)> f8 = make_function<char,int,int>(functionoid_overload{}); assert(f8(1,2,3)==6);
http://coliru.stacked-crooked.com/a/a9e0ad2a2da0bf1f lambda成功的唯一原因是因為它可以隱式轉換為函數指針,因為您的示例未捕獲任何狀態。 請注意,我的代碼要求重載函數的參數類型為具有重載operator()(包括bind)的函數類,但現在能夠推斷出所有非重載的函數類。
decltype
行很復雜,但是它們用於推導返回類型。 請注意,在我的所有測試中,我都不需要指定返回類型。 讓我們分解一下make_function<short,int,int>
,就像T
是char(*)(short, int, int)
:
-> decltype(t(std::declval<FirstArg>(), std::declval<Args>()...))(FirstArg, Args...)
`std::declval<FirstArg>()` is `short{}` (roughly)
-> decltype(t(short{}, std::declval<Args>()...))(FirstArg, Args...)
`std::declval<Args>()...` are `int{}, int{}` (roughly)
-> decltype(t(short{}, int{}, int{})(FirstArg, Args...)
`t(short{}, int{}, int{})` is an `int{}` (roughly)
-> decltype(short{})(FirstArg, Args...)
`decltype(int{})` is `int`
-> int(FirstArg, Args...)
`FirstArg` is still `short`
-> int(short, Args...)
`Args...` are `int, int`
-> int(short, int, int)
So this complex expression merely figures out the function's signature
well, that should look familiar...
通常,如果沒有嚴格的限制,即傳遞給make_function
任何內容只能使用一個簽名進行調用,就make_function
。
您將如何處理類似:
struct Generic
{
void operator()() { /* ... */ }
void operator()() const { /* ... */ }
template<typename T, typename... Ts>
T operator()(T&& t, Ts&&...) { /* ... */ }
template<typename T, typename... Ts>
T operator()(T&& t, Ts&&...) const { /* ... */ }
};
C ++ 14通用lambda將具有相同的問題。
std::function
的簽名是基於您計划如何調用它的,而不是基於您構造/分配它的方式。
您也不能為std::bind
解決它,因為它具有不確定的Arity:
void foo() { std::cout << "foo()" << std::endl; }
//...
auto f = std::bind(foo);
f(); // writes "foo()"
f(1); // writes "foo()"
f(1, 2, 3, 4, 5, 6); // writes "foo()"
您希望能夠將lambda轉換為std::function
的主要原因是因為您需要兩個重載,每個重載都具有不同的簽名。
解決此問題的好方法涉及std::result_of
。
假設您正在制作一個采用lambda或其他功能的循環控制結構。 如果該函數返回void,則您希望不受控制地循環。 如果返回bool
或類似內容,則要在返回true
進行循環。 如果它返回enum ControlFlow
,則您要注意ControlFlow
返回值(例如, continue
或break
)。 所討論的函數將要么遍歷元素,要么可選地獲取一些其他數據(迭代中的索引,或者有關元素的“位置”元信息,等等)。
std::result_of
將允許您假裝使用不同數量的參數來調用傳入的類型。 然后,一個traits類可以找出上述哪個簽名是“最佳匹配”,然后路由到處理該簽名的實現(可能是通過將“更簡單”的案例包裝在lambda中並調用更復雜的案例)。
make_function
,您的make_function
可能會出現此問題,因為然后您可以簡單地在各種std::function< blah(etc) >
情況下重載。 但是隨着auto
參數的傳遞, std::bind
已經完成了完美的轉發,因此只能處理最簡單的情況。
std::result_of
traits類(以及可能的相關概念匹配和requires
的條款)和標簽分派(或SFINAE作為最后的手段)。
最大的缺點是,您最終不得不自己半手動管理優先順序。 我可以在幫助程序類中看到一些實用程序,在其中您可以提供要匹配的簽名列表,它可以生成boost::variant
或者還可以生成規范輸出和該規范輸出的轉換方法。
簡短的答案? std::bind
的實現是特定於實現的細節,但它可能涉及可變參數包的完美轉發,因此不適合您“獲取唯一且唯一的operator()
”技術正在使用。
再舉一個例子:
template <typename A,typename B>
vector<B> map(std::function<B (A)> f, vector<A> arr) {
vector<B> res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
應寫為:
template<typename expression>
using result = typename std::result_of<expression>::type;
template<typename expression>
using decayed_result = typename std::decay<result<expression>>::type;
template <typename function,typename B>
vector<decayed_result<function(B)>> map(function&& f, vector<A> const& arr) {
vector<decayed_result<function(B)>> res;
res.reserve( arr.size() );
for (A const& a : arr) {
res.push_back( f(a) );
}
return res;
}
同樣, result_of
是正確的解決方案,而不是不必要地將其轉換為std::function
。
對於fold_right
我們得到:
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b,T>::type;
template<typename function, typename src, typename dest>
EnableIf<
std::is_convertible< result<function(src, dest)>, dest >::value,
std::vector<dest>
>
fold_right( function&& f, std::vector<src> const& v, dest initial )
再次跳過對f
任何類型擦除。 而且,如果您確實想在f
上鍵入擦除,則可以執行以下操作:
template<typename T> struct identity { typedef T type; };
template<typename T> using do_not_deduce = typename identity<T>::type;
template<typename src, typename dest>
std::vector<dest> fold_right( do_not_deduce< std::function<dest(src,dest)> > f, std::vector<src> const& v, dest init );
std::function
是類型擦除對象。 鍵入“擦除”是因為您想在不想攜帶該類型的地方使用該類型。 從類型中推斷出應該創建哪種類型的結果類型擦除對象幾乎總是錯誤的答案,因為您擁有非類型擦除案例的所有依賴關系,以及類型擦除的所有無效性。
make_function
的結果取決於源的完整類型,這使得類型擦除輸出幾乎完全無用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.