Suppose I have the following function, that takes a function as a parameter.
template <typename F> void test_func(F f) { // typedef typename function_traits<F>::return_type T; typedef int T; std::mt19937 rng(std::time(0)); std::uniform_int_distribution<T> uint_dist10(0, std::numeric_limits<T>::max()); f(uint_dist10(rng), uint_dist10(rng)); // Problem! }
Usage would be:
int foo(int, int) { return 0; } int bar(int, int, int, int) { return 0; } int main() { test_func(foo); // test_func(bar); }
Just like foo
and bar
, I have several functions that return T
, and take some amount of parameters of type T
. I would like test_func
to generate as many calls to my RNG as the function f takes parameters. In other words, we can assume T
is always an integer type, and that each parameter will be the same, i.e. a function call to an RNG.
Using function_traits (such as the ones in Boost), I can fetch the return type of F
, and that helps a little. Roughly, my question is
How can I generate a needed amount of function calls so that it matches the arity of the function F?
Before C++11, I would have looked at Boost.Preprocessor, or maybe relied on template specialization. Is there a nicer way of doing it now?
This limit is determined by stacked frame size, which is set by OS. Theoretically you can set these max stack size to 8192 bits. Each variable takes up 32 bits then you could pass 256 parameters.
Arity (/ˈærɪti/ ( listen)) is the number of arguments or operands taken by a function, operation or relation in logic, mathematics, and computer science. In mathematics, arity may also be named rank, but this word can have many other meanings in mathematics.
Arity (from Latin) is the term used to refer to the number of arguments or operands in a function or operation, respectively. You're most likely to come across this word in the realm of JavaScript when it's used to mention the number of arguments expected by a JavaScript function.
Fixed arity function is the most popular kind present in almos tall programming languages. Fixed arity function must be called with the same number of arguments as the number of parameters specified in its declaration. Definite arity function must be called with a finite number of arguments.
First define a meta function called arity
to compute arity of the function (it is just a simple implementation; can be improved to compute arity of functors also. See my answer here.):
template<typename F> struct arity; template<typename R, typename ...Args> struct arity<R (*)(Args...)> { static const std::size_t value = sizeof ... (Args); };
then define another meta function called genseq
to generate a compile time sequence of integral values:
template<int ... N> struct seq { using type = seq<N...>; template<int I> struct push_back : seq<N..., I> {}; }; template<int N> struct genseq : genseq<N-1>::type::template push_back<N-1> {}; template<> struct genseq<0> : seq<> {}; template<int N> using genseq_t = typename genseq<N>::type; //Just a friendly alias!
then a function invoker as:
template<typename F, typename ArgEvaluator, int ...N> void invoke(seq<N...>, F f, ArgEvaluator arg_evaluator) { using arg_type = decltype(arg_evaluator()); constexpr std::size_t arity = sizeof ... (N); arg_type args[] { (N, arg_evaluator()) ... }; //enforce order of evaluation f( args[N] ... ); }
And then your code would become this:
template <typename F> void test_func(F f) { // typedef typename function_traits<F>::return_type T; typedef int T; std::mt19937 rng(std::time(0)); std::uniform_int_distribution<T> uint_dist10(0, std::numeric_limits<T>::max()); //f(uint_dist10(rng), uint_dist10(rng)); // Problem! auto arg_evaluator = [&]() mutable { return uint_dist10(rng); }; invoke(genseq_t<arity<F>::value>(), f, arg_evaluator); }
Here is a sample demo.
Hope that helps.
No need for complicated meta calculations.
template <typename Ret, typename ... T> void test_func (Ret f (T...)) { std::mt19937 rng(std::time(0)); f((std::uniform_int_distribution<T>(0, std::numeric_limits<T>::max())(rng))...); } int moo(int, int, int){ return 0; } int main () { test_func(moo); }
To support functors one needs a bit longer implementation, still not too complicated:
// separate arguments type from function/functor type template <typename F, typename ... T> void test_func_impl (F f) { std::mt19937 rng(std::time(0)); f((std::uniform_int_distribution<T>(0, std::numeric_limits<T>::max())(rng))...); } // overload for a straight function template <typename Ret, typename ... T> void test_func (Ret f (T...)) { test_func_impl<decltype(f), T...>(f); } // forwarder for a functor with a normal operator() template <typename F, typename Ret, typename... T> void test_func_for_functor (F f, Ret (F::*)(T...)) { test_func_impl<F, T...>(f); } // forwarder for a functor with a const operator() template <typename F, typename Ret, typename... T> void test_func_for_functor (F f, Ret (F::*)(T...)const) { test_func_impl<F, T...>(f); } // overload for anything that has operator() template <typename F> void test_func (F f) { test_func_for_functor(f, &F::operator()); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With