Walter
Walter

Reputation: 45454

why is there no std::make_function()?

std::function<> is a useful wrapper around almost any callable thing, including free functions, lambdas, functors, member functions, results from std::bind. However, when creating a std::function<>, one must explicitly specify the function signature as in (taken from 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();

even though the signature could be inferred from the assigned objects. It seems that a natural way to avoid this (which should be quite handy in heavily templated code) is an overloaded function template make_function (similar in spirit to std::make_pair or std::make_tuple), when above examples would simply become

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

Another possible use case is to get the return type for callable object of any kind

decltype(make_function(function_object))::return_type;

avoiding the traits magic in the answer by Piotr S. to this question.

So, my question: why does the standard not provide this functionality? Can make_function be implemented without compiler magic? Or would it need compiler magic? (even then the first question remains.)

Upvotes: 19

Views: 4777

Answers (4)

thor
thor

Reputation: 22530

As commented here and elsewhere, there is the ambiguity issue that can confuse type inferencing. Probably these corner cases stopped a std::make_function from being adopted as it would not be able to resolve ambiguity, overloading or work nicely with C++ automatic type conversions. Another argument against it that I see a lot is that std::function has overhead (in type erasure), and many people are against using std::function on that basis for anything other than storage of callables.

However, for the non-ambiguous case, it is possible to write a make_function for lambdas and other callables that takes care of type inferencing, which avoids repeating function type signatures when there is really no ambiguity. One way to do it (taken from my related question) is as follows:

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

Upvotes: 12

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

The general case cannot work. There are specific cases (that support C++11 lambdas but not C++14, does not support bind, supports non-overloaded functions and methods, does not support function objects) where you can build a make_function that "works". There are also some functions you can write that are useful.

The make_function that "works" is usually a bad idea.

Just keep a copy of the original function object around if you don't need to convert it to a std::function<?>. You only need to convert it to a std::function<?> when you already know the types you are going to be passing to it, and what you are doing with the return type -- ie, when you are type-erasing around the signature.

std::function is not a "all purpose holder for a function type". It is a type-erasure class that is used to erase type information so you can have code that operates uniformly on it. If you are deducing the signature from the object, there is little if no reason to store it in a std::function at all.

There are narrow cases where it is useful, when you want to behave differently based on the input and output argument types of the function argument you are passed. In this case, your signature deduction facility could be useful: tying it to std::function would be a bad idea, in my opinion, as it ties together the two independent concepts (signature deduction and type erasure) in a way that is rarely useful.

In short, reconsider.


Now, I mentioned above that there are some useful utilities that could be called make_function. Here are two of them:

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

but requires that you list the arguments. It deduces return value.

This variant:

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

doesn't actually make a function, but lets you call a function with multiple std::function overloads and pick between them correctly. It is also invokable in a perfect-forwarding way back to the underlying F. In 97/100 cases, you won't be able to notice the difference between this make_function and one that returns an actual std::function (those last cases being cases where someone expects to type-deduce the std::function from the type, and perfect forwarding failures)

So:

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

fails to compile, while

int main() {
  std::cout << foo( make_function([]{}) ) << "\n";
}

succeeds. However, even this trick is just patching a hole in the design of std::function which I hope will be rolled into the post-concepts std::function. At that point, you may have well just store the original object.

In general, you cannot determine a single guaranteed unique signature for a callable object x or a function name x.

In the case of a callable object, the operator() could have multiple overloads. This can be done in C++14 with [](auto x) lambdas, and with function objects or the return from std::bind in C++03.

With the name of a function (or function pointer), the name does not correspond to a single object (or pointer). Resolution is done when it is passed to the std::function, and the correct overload is often picked (because std::function takes a R(*)(Args...) pointer, and maybe something similar for member functions (I cannot recall)).

Doing so with a make_function is nearly impossible.

Upvotes: 3

sbabbi
sbabbi

Reputation: 11191

Please notice that in all of your examples you can simply remove make_function and you get the same result, or actually more efficient, because invoking an std::function often requires a virtual call. So a first good point would be to discourage the usage of std::function when it is unnecessary.

Typically you use std::function as a member object of some class (callback et similia) or as argument of a function that can not be a template for whatever reason. In both of these cases make_function would be useless.

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.

There is only one case I could think of where you can really have a benefit: a std::function initialized by a ternary operator:

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

is probably better than

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

I believe that this is quite rare case, so the advantages of a make_function are minimal.

The real disadvantage, IMO, is that the simple existence of an hypothetical make_function would encourage less experienced developers to use std::function when it is not necessary, as you show in your code.

Upvotes: 1

Drax
Drax

Reputation: 13288

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

Because what would be the type of func here ?

This ambiguity applies to all functor objects (which includes bind return values and lambdas), which would make make_function only usable on function pointers.

Upvotes: 15

Related Questions