簡體   English   中英

c ++ 11:如何編寫包裝器函數以制作`std :: function`對象

[英]c++11: How to write a wrapper function to make `std::function` objects

我試圖寫一個包裝器make_function ,它像std::make_pair可以從合適的可調用對象中創建一個std::function對象。

就像make_pair一樣,對於函數指針fooauto f0 = make_function(foo); 創建具有正確類型簽名的std::function函數對象f0 只是為了澄清,我不介意偶爾將類型參數提供給make_function ,以防很難(或不可能)完全從參數推導類型。

到目前為止,我想出的東西(下面的代碼)對lambda,某些函數指針和函子(我沒有考慮過volatile)可以很好地工作。 但是我無法將其用於std::bindstd::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> ,就像Tchar(*)(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返回值(例如, continuebreak )。 所討論的函數將要么遍歷元素,要么可選地獲取一些其他數據(迭代中的索引,或者有關元素的“位置”元信息,等等)。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM