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

why is there no std::make_function()?

std::function<> 是对几乎所有可调用对象的有用包装,包括自由函数、lambda、仿函数、成员函数、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());

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

decltype(make_function(function_object))::return_type;

避免 Piotr S. 对 .

的回答中的特征魔法

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

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 return 值和 lambda),这将使 make_function 只能在函数指针上使用。

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

通常,您使用 std::function 作为某些 class(回调等)的成员对象,或者作为出于某种原因不能作为模板的函数的参数。在这两种情况下 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 的优势微乎其微。

在我看来,真正的缺点是假设 make_function 的简单存在会鼓励经验不足的开发人员在没有必要时使用 std::function,如您在代码中所示。

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

但是,对于非歧义的情况,可以为 lambda 和其他负责类型推断的可调用函数编写 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;
}

一般情况下不行。在特定情况下(支持 C++11 lambda 但不支持 C++14,不支持 bind,支持非重载函数和方法,不支持函数对象),您可以在其中构建 make_function 即 "works"。还有一些你可以写的有用的函数。

"works" make_function 通常是个坏主意

如果不需要将其转换为 std::function<?>,只需保留原始函数对象的副本即可。当您已经知道要传递给它的类型以及您正在使用 return 类型做什么时,您只需要将它转换为 std::function<?>——即,当您输入时——正在擦除签名周围。

std::function 不是 "all purpose holder for a function type"。它是一种类型擦除 class,用于擦除类型信息,因此您可以拥有对其进行统一操作的代码。如果您要从对象中推导出签名,则根本没有理由将其存储在 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);
}

但要求您列出参数。它推导出 return 值。

此变体:

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 重载的函数并在它们之间正确选择。它也可以以完美转发的方式调用回到底层 F。在 97/100 的情况下,您将无法注意到 make_function 与 return 实际 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 设计中的一个漏洞,我希望它能被纳入 post-概念 std::function。到那时,您可能只存储了原始对象。

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

在可调用对象的情况下,operator() 可以有多个重载。这可以在 C++14 中使用 [](auto x) lambda 和函数对象或 C++03 中 std::bind 中的 return 来完成。

同一个函数名(或函数指针),该名称不对应单个对象(或指针)。解析在传递给 std::function 时完成,并且通常会选择正确的重载(因为 std::function 需要一个 R(*)(Args...) 指针,成员函数可能有类似的东西(我不记得了) ).

使用 make_function 几乎是不可能的。