Lambda function with number of arguments determined at compile-time
Asked Answered
S

5

12

I would like to declare a lambda function with exactly N parameters, where N is a template argument. Something like...

template <int N>
class A {
    std::function<void (double, ..., double)> func;
                        // exactly n inputs
};

I could not think of a way to do this with the metafunction paradigm.

Sick answered 12/5, 2014 at 18:6 Comment(2)
Where is the lambda expression?Routh
The class will store a lambda expression. Let's say it is initialized in the constructor for definiteness (this is not necessarily true in my particular application).Sick
R
14

You can write a template n_ary_function with a nested typedef type. This type can be used as follows:

template <int N>
class A {
    typename n_ary_function<N, double>::type func;
};

The following code fragment contains the definition of n_ary_function:

template <std::size_t N, typename Type, typename ...Types>
struct n_ary_function
{
    using type = typename n_ary_function<N - 1, Type, Type, Types...>::type;
};

template <typename Type, typename ...Types>
struct n_ary_function<0, Type, Types...>
{
    using type = std::function<void(Types...)>;
};
Routh answered 12/5, 2014 at 18:28 Comment(0)
C
2

A meta template that takes a template, a count, and a type, and invokes the template with N copies of the type:

template<template<class...>class target, unsigned N, class T, class... Ts>
struct repeat_type_N: repeat_type_N<target, N-1, T, T, Ts...> {};
template<template<class...>class target, class T, class... Ts>
struct repeat_type_N<target, 0, T, Ts...> {
  typedef target<Ts...> type;
};
template<template<class...>class target, unsigned N, class T>
using repeat_type_N_times = typename repeat_type_N<target, N, T>::type;

Now, we use it:

template<typename... Ts> using operation=void(Ts...);
template<unsigned N, class T> using N_ary_op = repeat_type_N_times< operation, N, T >;
template<unsigned N> using N_double_func = N_ary_op<N,double>;

And we test it:

void three_doubles(double, double, double) {}

int main() {
  N_double_func<3>* ptr = three_doubles;
  std::function< N_double_func<3> > f = three_doubles;
}

and win.

What exactly you use the double, double, double for is completely up to you in the above system. You can have a lambda that you initialize a std::function with, for example.

You can pack up the double, double, double into a template<class...>struct type_list{}; so you can pass it as one argument to another template, then specialize to unpack it.

A repeat_type that has less recursion for large N:

// package for types.  The typedef saves characters later, and is a common pattern in my packages:
template<class...>struct types{typedef types type;};

// Takes a target and a `types`, and applies it.  Note that the base has no implementation
// which leads to errors if you pass a non-`types<>` as the second argument:
template<template<class...>class target, class types> struct apply_types;
template<template<class...>class target, class... Ts>
struct apply_types<target, types<Ts...>>{
  typedef target<Ts...> type;
};
// alias boilerplate:
template<template<class...>class target, class types>
using apply_types_t=typename apply_types<target,types>::type;

// divide and conquer, recursively:
template<unsigned N, class T, class Types=types<>> struct make_types:make_types<
  (N+1)/2, T, typename make_types<N/2, T, Types>::type
> {};

// terminate recursion at 0 and 1:
template<class T, class... Types> struct make_types<1, T, types<Types...>>:types<T,Types...> {};
template<class T, class Types> struct make_types<0, T, Types>:Types{};

// alias boilerplate:
template<unsigned N, class T>
using make_types_t=typename make_types<N,T>::type;

// all of the above reduces `repeat_type_N_t` to a one-liner:    
template<template<class...>class target, unsigned N, class T>
using repeat_type_N_times = apply_types_t<target, make_types_t<N,T>>;

For large N, the above can significantly reduce compile times, and deal with overflowing the template stack.

Censor answered 12/5, 2014 at 19:9 Comment(1)
@Routh stole your solution, make it all generic and stuff.Censor
H
1

For completeness, here is a solution without recursion:

template <class Ret, class Arg, class Idx>
struct n_ary_function_;

template <class Ret, class Arg, std::size_t... Idx>
struct n_ary_function_<Ret, Arg, std::index_sequence<Idx...>> {
    template <class T, std::size_t>
    using id = T;

    using type = std::function<Ret(id<Arg, Idx>...)>;
};

template <class Ret, class Arg, std::size_t N>
using n_ary_function = typename n_ary_function_<
    Ret, Arg, std::make_index_sequence<N>
>::type;

See it live on Coliru

Hardihood answered 3/10, 2018 at 12:47 Comment(2)
Your example link throws a fatal compiler error.Immanent
@Immanent that's on purpose: this is a trick to get the compiler to print out a type through an error message.Hardihood
I
0

You can't do this directly.

You can do something like this

template <unsigned N> class UniformTuple;

template <>
class UniformTuple <0>
{
};

template <unsigned N>
class UniformTuple : public UniformTuple <N-1>
{
public:

    template <typename... Args>
    UniformTuple (double arg, Args... args)
    : UniformTuple <N-1> (args...)
    , m_value (arg)
    {
    }

private:

    double m_value;
};

template <int N>
class A
{
    std :: function <void (const UniformTuple <N> &)> func;
};
Imagine answered 12/5, 2014 at 18:30 Comment(2)
+1. I thought about this approach. It's not ideal, but it may be the only way.Sick
Another idea is to define my own Function class with a virtual get(const double *arguments) and implement the operator() with a variadic template (and perform a static_assert in operator()). This is also not ideal, but a bit closer to what I need to be able to do.Sick
A
0

NoSid's solution is quite creative (essentially "appending" the types one by one). Another solution I concocted which might require a bit less mental effort is the following using std::index_sequence (which is a quite natural way to create an unknown sized parameter pack):

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

template<typename T, long U>
struct reduce 
{
    using type = T;
};

template<typename U, typename IndexSequence>
struct FunctionHolderImpl;

template<typename U, long ... Indices>
struct FunctionHolderImpl<U, std::index_sequence<Indices...>>
{
    using value = std::function<void(typename reduce<U, Indices>::type...)>;
};

template<long N>
struct FunctionHolder
{
    using func = FunctionHolderImpl<double, std::make_index_sequence<N>>::value;
};
Alpert answered 4/5 at 11:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.