Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 320
Walter Avatar asked Jan 07 '15 17:01

Walter


2 Answers

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.

like image 194
Drax Avatar answered Oct 19 '22 00:10

Drax


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;
}
like image 28
thor Avatar answered Oct 19 '22 02:10

thor