2
votes

I'm looking for a way to pass function pointers, functors or lambdas to a template function g which uses the passed function's argument types, for example:

template<class T1, class T2, class T3>
struct wrapper_t {
  boost::function<void(T1,T2,T3)> f;
  wrapper_t( boost::function<void(T1,T2,T3)> f ) : f(f) {}
  void operator( std::vector<T1> &a, std::vector<T2> &b, T3 c ) {
    assert(a.size() == b.size());
    for(size_t i = 0 ; i != a.size() ; i++) f(a[i], b[i], c);
  }
};
template<class T1, class T2, class T3>
wrapper_t<T1,T2,T3> make_wrapper( boost::function<void(T1,T2,T3)> f ) {
  return wrapper_t<T1,T2,T3>( f );
}

void f(int, double, char) {};
wrapper_t<int, double, char> w0(f); // need to repeat types

auto w1 = make_wrapper(f); // more comfortable

std::vector<int> a{{1, 2, 3}};
std::vector<double> b{{1.0, 2.0, 3.0}};
w0( a, b, 'c' );
w1( a, b, 'c' );

The make_wrapper function only exists to extract the types from the argument, some syntactic sugar to avoid having to type them twice.


A minimal example for my problem is this function:

template<class T>
void g1( const boost::function<void(T)> & ) {}

Using these as input

void f1(int) {}
struct f2_t { void operator()(int) {} };

it fails to infer T=int

f2_t f2;
g1( f1 ); // mismatched types ‘const std::function<void(T)>’ and ‘void(int)’
g1( f2 ); // ‘f2_t’ is not derived from ‘const std::function<void(T)>’
g1( [](int){} ); // ‘::<lambda(int)>’ is not derived from ‘…
g1<int>( f1 ); // ok
g1<int>( f2 ); // ok
g1<int>( [](int){} ); // ok

But T=int can be inferred from a plain function pointer, but of this doesn't work with the functor or lambda either:

template<class T>
void g2( void (*)(T) ) {}

g2( f1 ); // ok
g2( f2 ); // mismatched types …
g2<int>( f2 ); // ok
g2( [](int){} ); // mismatched types …
g2<int>( [](int){} ); // ok

Is there a way to infer T not just for plain function pointers but for functors and lambdas, too?

Or does it have to be something like this?

template<class F>
void g( F ) { typedef first_argument_of<F>::type T; }

(in my real code I need to deconstruct a function with four arguments this way, but std::function::…argument_type only exists for one or two arguments; boost::function has argN_type, but I don't think I can use that anyway since F is not always a function which is my problem, see above, etc)

3
Curious: where are you getting your T from to feed this template deduced std::function<void(T)> in order to invoke it? - Yakk - Adam Nevraumont

3 Answers

5
votes

There is no way to do what you want for a variety of reasons. But here is one that should make the problem pretty clear:

struct function_object
{
    template<typename ...T>
    void operator ()(T&&... v){}
};

f( function_object{} );

What is the type of the arguments of the function object passed to f? There isn't any, it can be called with any kind and number of arguments.

2
votes

I also think there is no direct way to define the template parameters and function arguments of a single, primary template definition such that T can be deduced in all the different situations (function pointer, lambda expression, std::function argument etc.).

I would therefore recommend that you follow the approach suggested at the end of your question. Indeed neither std::function nor the tools offered by Boost (as far as I know) will easily enable this, though.

What I use (and I learnt that from other SO posts in the past), is a rather complicated template function_traits with specializations for all the different cases. My definition is this:

template <typename T>
struct function_traits
  : public function_traits<decltype(&T::operator())>
{ };

template <typename Return, typename... Args>
struct function_traits<Return(Args...)>
{
  typedef std::size_t size_type;
  typedef Return      result_type;
  typedef result_type function_type(Args...);

  static constexpr size_type arity = sizeof...(Args);


  template <size_type index>
  struct arg
  {
    typedef typename std::tuple_element<index,std::tuple<Args...>>::type type;
  };

  static constexpr bool value = true;
};

template <typename Return, typename... Args>
struct function_traits<Return(*)(Args...)>
  : function_traits<Return(Args...)>
{ };

template <typename Return, typename... Args>
struct function_traits<Return(&)(Args...)>
  : function_traits<Return(Args...)>
{ };

template <typename Class, typename Return, typename... Args>
struct function_traits<Return(Class::*)(Args...)>
  : function_traits<Return(Args...)>
{ };

template <typename Class, typename Return, typename... Args>
struct function_traits<Return(Class::*)(Args...) volatile>
  : function_traits<Return(Args...)>
{ };

template <typename Class, typename Return, typename... Args>
struct function_traits<Return(Class::*)(Args...) const>
  : function_traits<Return(Args...)>
{ };

template <typename Class, typename Return, typename... Args>
struct function_traits<Return(Class::*)(Args...) const volatile>
  : function_traits<Return(Args...)>
{ };

To make using this even more convenient, you may want to define using-aliases:

template <typename Fun>
using result_of = typename function_traits<Fun>::result_type;

template <typename Fun, std::size_t index>
using arg = typename function_traits<Fun>::template arg<index>::type;

With all these definitions (which in the below, I assume you put into a separate header more_type_traits.hpp), you can then easily define your wrapper function as follows:

#include <iostream>
#include <functional>
#include <type_traits>
#include "more_type_traits.hpp"

template <typename Fun>
using noref = typename std::remove_reference<Fun>::type;

template <typename Fun>
result_of<noref<Fun>> fun(Fun &&argfun)
{
  // Default-initialize the first argument
  arg<noref<Fun>,0> arg {};

  // Call the function
  return argfun(arg);
}

The below (which is basically copied from your code) then compiles and works for me:

void f1(int i)
{ std::cout << "f1(" << i << ')' << std::endl; }

struct f2_t
{
  void operator()(int i)
  { std::cout << "f2(" << i << ')' << std::endl; }
};


int main()
{
  fun(f1);

  f2_t f2;
  fun(f2);

  std::function<void(int)> funobj = [](int i)
    { std::cout << "funobj(" << i << ')' << std::endl; };
  fun(funobj);

  fun( [](int i) { std::cout << "lambda(" << i << ')' << std::endl; } ); 

  return 0;
}

Clearly, the definition of function_traits is complicated, because many different specializations are required. But it's worth the effort if you want to make function wrapping convenient.

1
votes

Lets suppose I read in a comment that the OP really wants to take a function that mutates a T, and turn it into a function that mutates a std::vector<T>, and thinks that in order to do this you need to know what T is.

You don't

#include <type_traits>
#include <utility>

template<typename Lambda>
struct container_version {
  Lambda closure;
  container_version( container_version const& ) = default;
  container_version( container_version && ) = default;
  container_version( container_version & ) = default;

  template<typename U>
  container_version( U&& func ):closure(std::forward<U>(func)) {};

  // lets make this work on any iterable range:
  template<typename Container>
  void operator()( Container&& c ) const {
    for( auto&& x:c )
      closure(x);
  }
};

template<typename Lambda>
container_version< typename std::decay<Lambda>::type >
make_container_version( Lambda&& closure ) {
  return {std::forward<Lambda>(closure)};
}

#include <vector>
#include <iostream>
#include <functional>
#include <array>

int main() {
   std::vector<int> my_vec = {0, 1, 2, 3};
   for (auto x:my_vec)
      std::cout << x << ",";
   std::cout << "\n";
   make_container_version( []( int& x ) { x++; })( my_vec );

   for (auto x:my_vec)
      std::cout << x << ",";
   std::cout << "\n";

   // hey look, we can store it in a `std::function` if we need to:
   auto super_func = make_container_version( []( int& x ) { x++; } );
   std::function< void( std::vector<int>& ) > func = super_func;
   // and the same super_func can be used for a function on a different container:
   std::function< void( std::array<int,7>& ) > func2 = super_func;

   func(my_vec);
   for (auto x:my_vec)
      std::cout << x << ",";
   std::cout << "\n";
}

In fact, taking the argument and turning it into a std::function, or forcing it to be stored in a std::function, costs efficiency, increases the complexity of the code, and makes it unable to do things that it has no problem doing.

The above version, before it is packed into a std::function, can operate on sets, lists, vectors , raw C arrays, std::arrays, etc.