为什么没有std :: make_function()?

时间:2015-01-07 17:52:22

标签: c++ function c++11

std::function<>是几乎所有可调用事物的有用包装器,包括自由函数,lambdas,仿函数,成员函数,std::bind的结果。但是,在创建std::function<>时,必须明确指定函数签名,如 (取自here

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

void print_num(int i)
{ std::cout << i << '\n'; }

struct PrintNum {
    void operator()(int i) const
    { std::cout << i << '\n'; }
};

// store a free function
std::function<void(int)> f_display = print_num;

// store a lambda
std::function<void()> f_display_42 = []() { print_num(42); };

// store the result of a call to std::bind
std::function<void()> f_display_31337 = std::bind(print_num, 31337);

// store a call to a member function
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;

// store a call to a member function and object
using std::placeholders::_1;
std::function<void(int)> f_add_display2= std::bind( &Foo::print_add, foo, _1 );

// store a call to a member function and object ptr
std::function<void(int)> f_add_display3= std::bind( &Foo::print_add, &foo, _1 );

// store a call to a function object
std::function<void(int)> f_display_obj = PrintNum();

即使签名可以从指定的对象推断出来。似乎避免这种情况的一种自然方式(在严格模板化的代码中应该非常方便)是一个重载的函数模板make_function(在精神上类似于std::make_pairstd::make_tuple),当例子只会变成

// store a free function
auto f_display = make_function(print_num);

// store a lambda
auto f_display_42 = make_function([](){ print_num(42);});

// store the result of a call to std::bind
auto f_display_31337 = make_function(std::bind(print_num, 31337));

// store a call to a member function
auto f_add_display = make_function(&Foo::print_add);

// store a call to a member function and object
using std::placeholders::_1;
auto f_add_display2 = make_function(std::bind( &Foo::print_add, foo, _1));

// store a call to a member function and object ptr
auto f_add_display3 = make_function(std::bind( &Foo::print_add, &foo, _1));

// store a call to a function object
auto f_display_obj = make_function(PrintNum());

另一个可能的用例是获取任何类型的可调用对象的返回类型

decltype(make_function(function_object))::return_type;

避免Piotr S.回答this question的答案中的特质魔力。

所以,我的问题:为什么标准不提供此功能?可以在没有编译魔术的情况下实现make_function吗?还是需要编译魔术? (即便如此,第一个问题仍然存在。)

4 个答案:

答案 0 :(得分:14)

class multi_functor
{
  public:
    void operator()(int) { std::cout << "i'm int" << std::endl }
    void operator()(double) { std::cout << "i'm double" << std::endl }
};

int main(void)
{
  auto func = make_function(multi_functor());
}

因为这里func的类型是什么?

这种歧义适用于所有函子对象(包括bind返回值和lambdas),这将使make_function仅在函数指针上可用。

答案 1 :(得分:6)

正如此处和其他地方所评论的那样,存在可能会混淆类型推理的歧义问题。可能这些极端情况阻止了std::make_function被采用,因为它无法解决模糊,重载或与C ++自动类型转换很好地协作。我看到很多反对它的另一个论点是std::function有开销(在类型擦除中),并且很多人反对在此基础上使用std::function除了存储可调用的内容之外的任何其他内容。

但是,对于非模糊的情况,可以为lambdas和其他负责类型推理的callables写一个make_function,这样可以避免在没有歧义的情况下重复函数类型签名。一种方法(取自my related question)如下:

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

答案 2 :(得分:3)

一般情况不起作用。有一些特定情况(支持C ++ 11 lambdas但不支持C ++ 14,不支持bind,支持非重载函数和方法,不支持函数对象),你可以在其中构建{{1那&#34;工作&#34;。您还可以编写一些有用的功能。

&#34;工作的make_function&#34; 通常是个坏主意

如果您不需要将其转换为make_function,请保留原始功能对象的副本。当您已经知道要传递给它的类型以及使用返回类型执行的操作时,您只需要将其转换为std::function<?> - 即,当您在签名周围进行类型擦除时

std::function<?>不是&#34;功能类型的所有目的持有者&#34;。它是一个类型擦除类,用于擦除类型信息,因此您可以拥有在其上统一运行的代码。如果您要从对象中推断签名,则几乎没有理由将其存储在std::function中。

根据您传递的函数参数的输入和输出参数类型,当您希望表现不同时,有一些很有用的情况。在这种情况下,您的签名扣除工具可能很有用:在我看来,将它绑定到std::function将是一个坏主意,因为它将两个独立的概念(签名演绎和类型擦除)联系在一起,很少有用。

简而言之,重新考虑。


现在,我在上面提到过,有一些有用的实用程序可以称为std::function。以下是其中两个:

make_function

但要求您列出参数。它推断了返回值。

此变体:

template<class...Args, class F>
std::function< std::result_of_t< F&(Args...) >
make_function( F&& f ) {
  return std::forward<F>(f);
}

实际上并没有创建函数,但允许您调用具有多个template<class F> struct make_function_helper { F f; template<class...Args> std::result_of_t< (F&&)(Args...) > operator()(Args&&...args)&& { return std::forward<F>(f)(std::forward<Args>(args)...); } template<class...Args> std::result_of_t< (F const&)(Args...) > operator()(Args&&...args) const& { return f(std::forward<Args>(args)...); } template<class...Args> std::result_of_t< (F&)(Args...) > operator()(Args&&...args) & { return f(std::forward<Args>(args)...); } template<class R, class...Args, class=std::enable_if_t< std::is_convertible<decltype( std::declval<F&>(Args...) ), R>{} >> operator std::function<R(Args...)>()&&{ return std::forward<F>(f); } template<class R, class...Args, class=std::enable_if_t< std::is_convertible<decltype( std::declval<F&>(Args...) ), R>{} >> operator std::function<R(Args...)>()const&{ return f; } }; template<class F> make_function_helper<F> make_function(F&&f) { return {std::forward<F>(f)}; } 重载的函数,并在它们之间正确选择。它也可以以完美转发的方式返回到底层std::function。在97/100的情况下,您无法注意到此F与返回实际make_function的情况之间存在差异(最后一种情况是有人希望对其进行类型推断的情况来自类型的std::function,以及完美的转发失败)

所以:

std::function

无法编译,而

int foo(std::function< int(int) >) { return 0; }
int foo(std::function< void() >) { return 1; }
int main() {
  std::cout << foo( []{} ) << "\n";
}

成功。然而,即便是这个技巧也只是在int main() { std::cout << foo( make_function([]{}) ) << "\n"; } 的设计中修补了一个漏洞,我希望这个漏洞可以归入后概念std::function。此时,您可能只需存储原始对象。

通常,您无法为可调用对象x或函数名称x确定单个保证唯一签名。

对于可调用对象,std::function可能有多个重载。这可以在C ++ 14中使用operator() lambdas,使用函数对象或在C ++ 03中从[](auto x)返回。

使用函数(或函数指针)的名称,该名称不对应于单个对象(或指针)。解析在传递给std::bind时完成,并且经常选择正确的重载(因为std::function采用std::function指针,对于成员函数可能类似(我不记得) )。

使用R(*)(Args...)这样做几乎是不可能的。

答案 3 :(得分:1)

请注意,在所有示例中,您只需删除make_function即可获得相同的结果,或实际上更高效,因为调用std::function通常需要进行虚拟呼叫。因此,第一个好处是在不必要时阻止使用std::function

通常,您使用std::function作为某个类的成员对象(callback et similia)或作为函数的参数,无论出于何种原因都不能作为模板。在这两种情况下make_function都没用。

struct Foo
{
     std::function<int()> callback
};
Foo x; x.callback = [](){return 0;} // No need for make_function

void bar( std::function<int(int)> f );
bar( [](int i){ return i;} ); // No need for make function.

我只能想到一个案例,你可以从哪里获得一个好处:由三元运营商初始化std::function

 auto f = val ? make_function( foo ) : make_function( bar );

可能比

更好
 auto f = val ? std::function<int()>( foo ) : std::function<int()>( bar );

我认为这种情况非常罕见,因此make_function的优势很小。

IMO的真正劣势在于假设make_function的简单存在会鼓励经验不足的开发人员在没有必要的情况下使用std::function,如您在代码中所示。