Can I declare a template to only accept functions with homogeneous signature?

Viewed 76

I have a set of duple of functions, each taking N arguments of type T, with an additional argument of type X for the second one.

For N=2, T=int and X=std::vector<int> the duple would be something like this:

void my_function2(int x1, int x2) {}
void my_function2(int x1, int x2, std::vector<int> other) {}

I am trying to write a templated high order function to which I pass the first function.

This will work for N=2:

template <typename R, typename T>
int my_hof(R(*param)(T, T)) { param(1,2); }

my_hof(&my_function2); // compiles

Making the signature of param explicit is important because otherwise the template instantiation would fail because of "Unresolved overloaded function type". The point is to eliminate the second overload of my_function2().

My problem is that I can not think of a way to generalize this to N. I tried using a variadic template:

template <typename R, typename ...X> 
struct make_signature { using type = R(*)(int, int); };
// Cheating a big on make_signature not to clutter the question
// But the idea would be to repeat the same type N times.

template <typename R, typename ...X>
int my_generalized_hof(typename make_signature<R, X...>::type param) {
    f(1,2); // cheating a bit on the call too 
}

my_generalized_hof(&my_function2); // does not compile

I guess the compiler gets confused as the type of param is not given straightaway, it can't decide which overload to use to do the type computation. ("unresolved overloaded function type again"). I think it would also refuse to do the type computation anyway as it could not deduce the type of R.

I can't think of a way to generate the signature.

Is it possible to write this in C++ ?

(Note that I know I could select the overload by casting my_function2() to the right type when calling my_generalized_hof(), but I'd like to avoid that)

1 Answers

I propose the following make_signature

template <typename T, std::size_t>
using getType = T;

template <typename, typename, std::size_t N,
          typename = std::make_index_sequence<N>>
struct make_signature;

template <typename R, typename T, std::size_t N, std::size_t ... Is>
struct make_signature<R, T, N, std::index_sequence<Is...>>
 { using type = R(*)(getType<T, Is>...); };

But there is a problem: when you write

template <typename R, typename T, std::size_t N>
void my_generalized_hof (typename make_signature<R, T, N>::type param)
 {
   // do something with param
 }

the make<R, T, N> part is in no-deduced-context (is before ::), so R, T, and N can't be deduced from the argument.

I mean... you can't call

 my_generalized_hof(&my_function2); 

You have to explicit the template arguments as follows

 // ...............VVVVVVVVVVVVVVV
 my_generalized_hof<void, int, 2u>(&my_function2); 

The following is a full compiling example

#include <utility>
#include <vector>
#include <type_traits>

void my_function2(int x1, int x2) {}
void my_function2(int x1, int x2, std::vector<int> other) {}

template <typename T, std::size_t>
using getType = T;

template <typename, typename, std::size_t N,
          typename = std::make_index_sequence<N>>
struct make_signature;

template <typename R, typename T, std::size_t N, std::size_t ... Is>
struct make_signature<R, T, N, std::index_sequence<Is...>>
 { using type = R(*)(getType<T, Is>...); };

template <typename R, typename T, std::size_t N>
void my_generalized_hof (typename make_signature<R, T, N>::type param)
 {
   // do something with param
 }

int main ()
 {
   my_generalized_hof<void, int, 2u>(&my_function2); 
 }
Related