为什么没有std::make_function()

why is there no std::make_function()?

本文关键字:function make std 为什么      更新时间:2023-10-16
std::function<>是一个

有用的包装器,几乎可以调用任何可调用的东西,包括自由函数、lambda、函子、成员函数、std::bind 结果。但是,在创建std::function<>时,必须显式指定函数签名,如(摘自此处)

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;

避免了皮奥特·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返回值和 lambda),这将使make_function只能在函数指针上使用。

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

但是,对于非歧义情况,可以为 lambda 和其他可调用对象编写一个负责类型推断的make_function,从而避免在实际上没有歧义时重复函数类型签名。一种方法(取自我的相关问题)如下:

#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。 您还可以编写一些有用的函数。

"有效"make_function通常是一个坏主意

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

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

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

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

使用函数(或

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

make_function这样做几乎是不可能的。

请注意

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

通常,您将std::function用作某个类(回调和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,如代码所示。