6

Given the type of a callable function C, I want to get at compile time a std::function; the type of which:

  • has the same return type of function C
  • the argument types are the first N argument types of function C

This means that, for a given type void(int, char, double) and a given N, the type of the function is:

  • N = 1 => result type: std::function<void(int)>
  • N = 2 => result type: std::function<void(int, char)>
  • N = 3 => result type: std::function<void(int, char, double)>
  • N > 3 => compile time error

Example:

template<std::size_t N, typename R, typename... A>
constexpr auto get() {
    return /*(magically somehow)*/ std::function<R(FirstNFromA...)>
}

template<std::size_t N, typename R, typename... A>
struct S {
    using func = decltype(get<N, R, A...>());
};
Braiam
  • 1
  • 11
  • 47
  • 78
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Nice Q & its solution. Seems like a coding delight. However, I fail to understand, where this will be used practically? In a real application, why someone would want to use `FuncType<2, void(int, char, double, int)>::Type` instead of simple `std::function`! Even inside the templates, there seems hardly a need to do such thing. To solve a tough problem there are 2 ways: Either derive equally tough solution or make the problem simpler. :-) – iammilind May 31 '16 at 12:50
  • @iammilind I had the problem during an attempt to design something with a bunch of mixins. One of the mixin gets a variadic list of types, the last of which is the one from which to inherit. It goes without saying that the first `N-1` must be used as arguments for a function type. :-) – skypjack May 31 '16 at 13:45

4 Answers4

6

It follows a possible solution:

#include <tuple>
#include <utility>
#include <functional>
#include <type_traits>

template<
    typename R,
    typename... A,
    std::size_t... I,
    std::enable_if_t<(sizeof...(I)<=sizeof...(A))>* = nullptr
> constexpr auto
get(std::integer_sequence<std::size_t, I...>) {
    return std::function<R(std::tuple_element_t<I, std::tuple<A...>>...)>{};
}

template<std::size_t, typename>
struct FuncType;

template<std::size_t N, typename R, typename... A>
struct FuncType<N, R(A...)> {
    using Type = decltype(get<R, A...>(std::make_index_sequence<N>{}));
};

int main() {
    static_assert(
        std::is_same<
            FuncType<2, void(int, char, double, int)>::Type,
            std::function<void(int, char)>
        >::value,
        "!"
    );
}

The basic idea is to use a tuple and those utilities that are part of the Standard template Library (as an example, std::tuple_element), mix them with a pack expansion placed in the right place and that's all.
To do that, I used a constexpr function that returns an empty std::function object of the given type. Then the type of the function is taken by means of a decltype and exported as a type of the FuncType object using an alias.
Everything takes place at compile time.
In order to deduce the right type for that function, I used a well known pattern that involves a std::integer_sequence and actually unpack the types of a tuple by expanding the indexes.

skypjack
  • 49,335
  • 19
  • 95
  • 187
1

Another solution could be:

#include <tuple>
#include <utility>
#include <functional>
#include <type_traits>

template <size_t N, class R, class Pack, class ResultPack, class Voider>
struct FuncTypeImpl;

template <size_t N, class R, template <class...> class Pack, class First, class... Args, class... ResultArgs>
struct FuncTypeImpl<N, R, Pack<First, Args...>, Pack<ResultArgs...>,  std::enable_if_t<(N > 0)>>: FuncTypeImpl<N-1, R, Pack<Args...>, Pack<ResultArgs..., First>, void> {
   using typename FuncTypeImpl<N-1, R, Pack<Args...>, Pack<ResultArgs..., First>, void>::Type;
};

template <size_t N, class R, template <class...> class Pack, class... Args, class... ResultArgs>
struct FuncTypeImpl<N, R, Pack<Args...>, Pack<ResultArgs...>, std::enable_if_t<(N == 0)>> {
   using Type = std::function<R(ResultArgs...)>;
};

template<std::size_t, typename>
struct FuncType;

template<std::size_t N, typename R, typename... A>
struct FuncType<N, R(A...)> {
    using Type = typename FuncTypeImpl<N, R, std::tuple<A...>, std::tuple<>, void>::Type;
};

int main() {
    static_assert(
        std::is_same<
            FuncType<3, void(int, char, double, int)>::Type,
            std::function<void(int, char, double)>
        >::value,
        "!"
    );
}

Edit: Yet another maybe a little bit simpler (that does not need a std::tuple) solution:

#include <utility>
#include <functional>
#include <type_traits>

template <class T>
struct ResultOf;

template <class R, class... Args>
struct ResultOf<R(Args...)> {
   using Type = R;
};

template<std::size_t N, class Foo, class ResultFoo = typename ResultOf<Foo>::Type() , class Voider = void>
struct FuncType;

template<std::size_t N, class R, class First, class... Args, class... ResultArgs >
struct FuncType<N, R(First, Args...), R(ResultArgs...), std::enable_if_t<(N > 0)>>: FuncType<N-1, R(Args...), R(ResultArgs..., First), void> {
};

template<std::size_t N, class R, class First, class... Args, class... ResultArgs >
struct FuncType<N, R(First, Args...), R(ResultArgs...), std::enable_if_t<(N == 0)>> {
   using Type = std::function<R(ResultArgs...)>;
};

int main() {
    static_assert(
        std::is_same<
            FuncType<3, void(int, char, double*, int)>::Type,
            std::function<void(int, char, double*)>
        >::value,
        "!"
    );
}
W.F.
  • 13,888
  • 2
  • 34
  • 81
1

Another tuple based solution.

Should work with C++11 too.

I suppose it can be simplified but I don't know how to avoid the use of the bool template parameter (I'm just learning C++11).

#include <tuple>
#include <utility>
#include <functional>
#include <type_traits>


template<std::size_t N, bool Z, typename R, typename...>
struct FTH1;

template <typename R, typename... A, typename... B>
struct FTH1<0U, true, R, std::tuple<A...>, B...>
 { using type = decltype(std::function<R(A...)>{}); };

template <std::size_t N, typename R, typename... A, typename B0, typename... B>
struct FTH1<N, false, R, std::tuple<A...>, B0, B...>
 { using type = typename FTH1<N-1U, (N-1U == 0U), R, std::tuple<A..., B0>, B...>::type; };


template <std::size_t N, typename>
struct FuncType;

template<std::size_t N, typename R, typename... A>
struct FuncType<N, R(A...)>
 { using Type = typename FTH1<N, (N == 0), R, std::tuple<>, A...>::type; };



int main() {
    static_assert(
        std::is_same<
            FuncType<2, void(int, char, double, int)>::Type,
            std::function<void(int, char)>
        >::value,
        "!"
    );
}

p.s.: sorry for my bad English.

max66
  • 65,235
  • 10
  • 71
  • 111
1

As I mentioned in the comments, if such problem arises then my first approach would be to change the problem itself! Sometimes, instead of finding a "tough solution" for a "tough problem", it's better to make the problem itself "simpler"!
There should never be a need, where one has to write FuncType<2, R(X,Y,Z)>::type instead of simple std::function<R(X,Y)>.

Above is my real answer. To solve your problem as a coding delight, I am putting a simple macro based answer. Instead of compile time, it will give you the required type at preprocessing time.

#define F(R, ...) std::function<R(__VA_ARGS__)>  // shorthand for std::function...
#define FuncType(N, R, ...) FUNC_##N(R, __VA_ARGS__)  // Main type
#define FUNC_0(R, ...) F(R)  // overload for 0 arg
#define FUNC_1(R, _1, ...) F(R, _1)  // overload for 1 arg
#define FUNC_2(R, _1, _2, ...) F(R, _1, _2)  // overload for 2 args
#define FUNC_3(R, _1, _2, _3, ...) F(R, _1, _2, _3)  // overload for 3 args

Usage:

int main() {
  static_assert(std::is_same<
    FuncType(2, void, int, char, double, int),  // <--- see usage
    std::function<void(int, char)>
    >::value, "!");  
}

As you can see there is a little change in usage. Instead of FuncType<2, void(int, char, double, int)>::type, I am using FuncType(2, void, int, char, double, int). Here is the demo.


As of now, the FUNC_N macros are overloaded upto 3 arguments. For more arguments, if we want to avoid copy pastes, then we can generate a header file with a simple program:

  std::ofstream funcN("FUNC_N.h"); // TODO: error check for argv & full path for file
  for(size_t i = 0, N = stoi(argv[1]); i < N; ++i)
  {
    funcN << "#define FUNC_" << i << "(R";  // FUNC_N
    for(size_t j = 1; j <= i; ++j)
      funcN << ", _" << j;  // picking up required args
    funcN << ", ...) ";  // remaining args

    funcN << "F(R";  // std::function
    for(size_t j = 1; j <= i; ++j)
      funcN << ", _" << j;  // passing only relevant args
    funcN <<")\n";
  }

And simply #include it:

#define F(R, ...) std::function<R(__VA_ARGS__)>
#define FuncType(N, R, ...) FUNC_##N(R, __VA_ARGS__)
#include"FUNC_N.h"

Here is the demo for how to generate the header file.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • As already mentioned in the comments, the need is for when you has `R(Args...)`, where you don't know neither which is the size of the parameter pack not which are the actual types. The only thing you know is that you don't want all of them, that's all. :-) ... Anyway, thank you for the answer. Not much scalable as a solution, but it works. – skypjack Jun 01 '16 at 06:33
  • @skypjack, `"neither which is the size of the parameter pack not which are the actual types. The only thing you know is that you don't want all of them"`. My point is that, you may want to design your project in such a way, that such requirement itself don't arise. However, I am not being judgmental about the genuineness of your need here. Just an opinion. Your solution is ofcourse elegant than macro style, as it doesn't rely on anything else. However, above macro solution is also scalable. You just have to create the header file using that small code, which involves all the macros. – iammilind Jun 01 '16 at 09:16