c ++ 11:如何编写包装函数来生成`std :: function`对象

时间:2014-02-12 20:21:15

标签: c++ c++11 c++14 std-function

我正在尝试编写一个包装器make_function,它与std::make_pair一样可以用合适的可调用对象创建一个std::function对象。

就像make_pair一样,对于函数指针fooauto f0 = make_function(foo);创建一个正确类型签名的std::function函数对象f0。 为了澄清,我不介意偶尔给make_function类型参数,以防很难(或不可能)从参数中完全推断出类型。

到目前为止我想出的(下面的代码)适用于lambdas,一些函数指针和functor(我没有考虑挥发性)。但我无法让std::bindstd::bind<R>结果发挥作用。在下面的代码中

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

无法编译/工作。我猜我没有正确捕获operator()结果的bind,但我不确定如何修复它。

有关如何解决此案例或改善其他角落案例的任何帮助表示赞赏。

我的问题当然是如何解决以下示例中的错误。

对于后台,我可以在这个问题中找到我使用此包装器的一个案例:How to make C++11 functions taking function<> parameters accept lambdas automatically。如果您不批准使用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;
}

Ideone

3 个答案:

答案 0 :(得分:7)

问题是你的代码没有正确处理lambdas,bind或functionoids,你的代码假设所有这些代码都没有参数。要处理这些,您必须指定参数类型:

//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...

答案 1 :(得分:6)

一般情况下,如果没有严格的限制,你无法解决它,无论你传递给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解决它,因为它具有不确定性:

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()"

答案 2 :(得分:2)

您希望能够将lambda转换为std::function的一个重要原因是因为您需要两个重载,每个重载都采用不同的签名。

解决此问题的好方法是std::result_of

假设您正在创建一个采用lambda或其他函数的循环控制结构。如果该函数返回void,则您希望循环不受控制。如果它返回bool之类,则需要在返回true时循环。如果它返回enum ControlFlow,您需要关注ControlFlow返回值(continuebreak,例如)。有问题的函数需要迭代的元素,以及可选的一些额外数据(迭代中的索引,可能是关于元素的一些“位置”元信息等)。

std::result_of会让你假装用不同数量的参数调用传入的类型。然后,traits类可以找出上述哪个签名是“最佳匹配”,然后路由到处理该签名的实现(可能通过将“更简单”的情况包装在lambda中并调用更复杂的情况)。 / p>

天真地,你的make_function会出现这个问题,因为你可以简单地重载各种std::function< blah(etc) >案例。但随着auto参数进入管道,std::bind已经完成转发,这只能处理最简单的情况。

std::result_of特征类(以及可能相关的概念匹配和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类型的擦除对象。您键入erase是因为您希望在某个地方使用不希望将类型转移到的地方。从类型中推断出你应该创建什么样的结果类型擦除对象几乎总是错误的答案,因为你具有非类型擦除情况的所有依赖性,以及类型擦除的所有低效率。

make_function的结果取决于源的完整类型,这使得类型擦除输出几乎完全没用。