简体   繁体   English

c ++ 11:如何编写包装器函数以制作`std :: function`对象

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

I am trying to write a wrapper make_function , which like std::make_pair can create a std::function object out of suitable callable objects. 我试图写一个包装器make_function ,它像std::make_pair可以从合适的可调用对象中创建一个std::function对象。

Just like make_pair , for a function pointer foo , auto f0 = make_function(foo); 就像make_pair一样,对于函数指针fooauto f0 = make_function(foo); creates a std::function function object f0 of the right type signature. 创建具有正确类型签名的std::function函数对象f0 Just to clarify, I don't mind occasionally giving type parameters to make_function in case it is difficult (or impossible) to deduce the type entirely from the parameters. 只是为了澄清,我不介意偶尔将类型参数提供给make_function ,以防很难(或不可能)完全从参数推导类型。

What I came up with so far (code below) works fine for lambdas, some function pointers, and functors (I didn't consider volatiles). 到目前为止,我想出的东西(下面的代码)对lambda,某些函数指针和函子(我没有考虑过volatile)可以很好地工作。 But I couldn't get it work for std::bind or std::bind<R> results. 但是我无法将其用于std::bindstd::bind<R>结果。 In the code below 在下面的代码中

auto f2 = make_function(std::bind(foo,_1,_2,_3)); //not OK

wouldn't compile/work, with gcc 4.8.1. 使用gcc 4.8.1无法编译/工作。 I am guessing that I didn't capture the operator() for the bind result correctly, but I am not sure how to fix it. 我猜我没有正确捕获bind结果的operator() ,但是我不确定如何解决它。

Any help on how to fix this case or improvement in other corner cases is appreciated. 感谢您提供有关如何解决此问题或在其他极端情况下进行改进的帮助。

My question is, of course, how to fix the error in the example below. 我的问题是,当然,如何修复以下示例中的错误。

For background, one of the cases I use this wrapper can be found at this question: How to make C++11 functions taking function<> parameters accept lambdas automatically . 对于背景,我可以在以下问题中找到我使用此包装器的情况之一: 如何使带有function <>参数的C ++ 11函数自动接受lambda If you do not approve the use of std::function or my specific way of using it, please leave your comments in that post, and discuss technical issues here. 如果您不同意使用std::function或我的特定使用方式,请在该帖子中留下您的评论,并在此处讨论技术问题。

--- EDIT --- -编辑-

From some of the comments, I learned that it's because of the ambiguity issue (ambiguity of the function call operator() of std::bind results). 从一些评论中,我了解到这是由于歧义问题( std::bind结果的函数调用operator()的歧义)造成的。 As pointed out by @Mooing Duck's answer, the solution is to give the parameter types explicitly. 正如@Mooing Duck的答案所指出的,解决方案是显式地指定参数类型。 I have updated the code to combine the three functions in @Mooing Duck's answer (with slight change of type parameters), so that the make_function wrapper can now handle/type-deduce unambiguous cases as before, and allow specification of complete type signature when there is ambiguity. 我已经更新了代码,以结合@Mooing Duck的答案中的三个函数(类型参数稍有变化),以便make_function包装器现在可以像以前一样处理/类型推断明确的情况,并允许在存在时指定完整的类型签名是模棱两可的。

(My original code for the unambiguous cases is at: https://stackoverflow.com/a/21665705/683218 and can be tested at: https://ideone.com/UhAk91 ): (我针对这些情况的原始代码位于: 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;
}

Ideone 伊迪奥

The problem is your code doesn't handle lambdas, bind, or functionoids properly, your code assumes that all of these take no parameters. 问题是您的代码无法正确处理lambda,bind或functionoid,因此您的代码假定所有这些都不带参数。 To handle these, you'll have to specify the parameter types: 要处理这些,您必须指定参数类型:

//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)};}

Variables: 变量:

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);

Tests: 测试:

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 The only reason your lambda was succeeding is because it was implicitly convertible to a function pointer because your example doesn't capture any state. http://coliru.stacked-crooked.com/a/a9e0ad2a2da0bf1f lambda成功的唯一原因是因为它可以隐式转换为函数指针,因为您的示例未捕获任何状态。 Note that my code requires the parameter types for overloaded functions, functionoids with overloaded operator() (including bind), but is now able to deduce all non-overloaded functionoids. 请注意,我的代码要求重载函数的参数类型为具有重载operator()(包括bind)的函数类,但现在能够推断出所有非重载的函数类。

The decltype lines are complicated but they're used to deduce the return types. decltype行很复杂,但是它们用于推导返回类型。 Notice that in NONE of my tests do I need to specify the return type. 请注意,在我的所有测试中,我都不需要指定返回类型。 Let's break down make_function<short,int,int> down as if T is char(*)(short, int, int) : 让我们分解一下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...

In general you cannot solve it without the severe restriction that whatever you pass to make_function is only callable with exactly one signature. 通常,如果没有严格的限制,即传递给make_function任何内容只能使用一个签名进行调用,就make_function

What are you going to do with something like: 您将如何处理类似:

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 generic lambdas will have the same issue. C ++ 14通用lambda将具有相同的问题。

The signature in std::function is based on how you plan to call it and not on how you construct/assign it. std::function的签名是基于您计划如何调用它的,而不是基于您构造/分配它的方式。

You cannot solve it for std::bind either, as that has indefinite arity: 您也不能为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()"

The big reason why you want to be able to convert lambdas to std::function is because you want two overloads, each taking different signatures. 您希望能够将lambda转换为std::function的主要原因是因为您需要两个重载,每个重载都具有不同的签名。

A good way to solve this involves std::result_of . 解决此问题的好方法涉及std::result_of

Suppose you are making a loop control structure that takes a lambda or other functional. 假设您正在制作一个采用lambda或其他功能的循环控制结构。 If that functional returns void, you want to loop uncontrolled. 如果该函数返回void,则您希望不受控制地循环。 If it returns bool or the like, you want to loop while it returns true . 如果返回bool或类似内容,则要在返回true进行循环。 If it returns enum ControlFlow , you want to pay attention to the ControlFlow return value ( continue or break , say). 如果它返回enum ControlFlow ,则您要注意ControlFlow返回值(例如, continuebreak )。 The function in question takes either the element iterating over, and optionally some extra data (the index in the iteration, maybe some "location" meta-information about the element, etc). 所讨论的函数将要么遍历元素,要么可选地获取一些其他数据(迭代中的索引,或者有关元素的“位置”元信息,等等)。

std::result_of would let you pretend to invoke the passed in type with a different number of arguments. std::result_of将允许您假装使用不同数量的参数来调用传入的类型。 A traits class could then figure out which of the above signatures is the "best match", and then route to the implementation that handles that signature (possibly by wrapping the "simpler" cases in a lambda and calling the more complex cases). 然后,一个traits类可以找出上述哪个签名是“最佳匹配”,然后路由到处理该签名的实现(可能是通过将“更简单”的案例包装在lambda中并调用更复杂的案例)。

Naively, your make_function would could this problem, because you could then simply overload on the various std::function< blah(etc) > cases. make_function ,您的make_function可能会出现此问题,因为然后您可以简单地在各种std::function< blah(etc) >情况下重载。 But with auto parameters coming down the pipe, and std::bind already doing perfect forwarding, this only handles the easiest cases. 但是随着auto参数的传递, std::bind已经完成了完美的转发,因此只能处理最简单的情况。

std::result_of traits classes (and possibly related concept matching and requires clauses) and tag dispatching (or SFINAE as a last resort). std::result_of traits类(以及可能的相关概念匹配和requires的条款)和标签分派(或SFINAE作为最后的手段)。

The big downside is that you end up having to manage the override order yourself semi-manually. 最大的缺点是,您最终不得不自己半手动管理优先顺序。 I could see some utility in helper classes where you provide a list of signatures to match, and it either produces a boost::variant or you also produce a canonical output and a conversion method to that canonical output. 我可以在帮助程序类中看到一些实用程序,在其中您可以提供要匹配的签名列表,它可以生成boost::variant或者还可以生成规范输出和该规范输出的转换方法。

The short answer? 简短的答案? std::bind 's implementation is implementation specific details, but it may involve the equivalent of perfect forwarding of variadic parameter packs, and as such is not suitable for your "get the address of the one and only concrete operator() " technique you are using. std::bind的实现是特定于实现的细节,但它可能涉及可变参数包的完美转发,因此不适合您“获取唯一且唯一的operator() ”技术正在使用。

As another example: 再举一个例子:

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;
}

should be written as: 应写为:

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;
}

again, result_of is the right solution, not converting things needlessly to std::function . 同样, result_of是正确的解决方案,而不是不必要地将其转换为std::function

For fold_right we get: 对于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 )

which again skips any type erasure on f . 再次跳过对f任何类型擦除。 And if you really want to do type erasure on f , you can do: 而且,如果您确实想在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 is a type erasure object. std::function类型擦除对象。 You type erase because you want to use a type somewhere you do not want to carry the type over to. 键入“擦除”是因为您想在不想携带该类型的地方使用该类型。 Deducing from a type what kind of resulting type erasure object you should create is almost always the wrong answer, because you have all of the dependency of non-type erased cases, and all of the inefficiency of the type erasure. 从类型中推断出应该创建哪种类型的结果类型擦除对象几乎总是错误的答案,因为您拥有非类型擦除案例的所有依赖关系,以及类型擦除的所有无效性。

make_function 's result is dependent the full type of the source, which makes type erasure output almost completely useless. make_function的结果取决于源的完整类型,这使得类型擦除输出几乎完全无用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM