Как я могу сгенерировать столько параметров, сколько есть арность функции?
Предположим, что у меня есть следующая функция, которая принимает функцию как параметр.
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!
}
Использование:
int foo(int, int) { return 0; }
int bar(int, int, int, int) { return 0; }
int main()
{
test_func(foo);
// test_func(bar);
}
Так же, как foo
и bar
, у меня есть несколько функций, которые возвращают T
, и берут некоторое количество параметров типа T
. Я хотел бы, чтобы test_func
генерировал столько звонков на мой RNG, как функция f принимает параметры. Другими словами, мы можем предположить, что T
всегда является целым типом и каждый параметр будет одним и тем же, то есть вызовом функции для RNG.
Используя функции function_traits (такие как те, что есть в Boost), я могу получить возвращаемый тип F
, и это немного помогает. Грубо говоря, мой вопрос:
Как я могу генерировать необходимое количество вызовов функций, чтобы он соответствовал арности функции F?
До С++ 11 я бы посмотрел на Boost.Preprocessor или, возможно, полагался на специализированную специализацию. Есть ли лучший способ сделать это сейчас?
Ответы
Ответ 1
Сначала определите мета-функцию, называемую arity
, чтобы вычислить arity функции (это просто простая реализация, может быть также улучшена для вычисления ясности функторов. См. мой ответ здесь):.
template<typename F>
struct arity;
template<typename R, typename ...Args>
struct arity<R (*)(Args...)>
{
static const std::size_t value = sizeof ... (Args);
};
затем определите другую мета-функцию, называемую genseq
, чтобы сгенерировать временную последовательность компиляции интегральных значений:
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!
то функция invoker как:
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] ... );
}
И тогда ваш код станет следующим:
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);
}
Ниже приведен пример демонстрации.
Надеюсь, что это поможет.
Ответ 2
Нет необходимости в сложных мета-вычислениях.
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);
}
Для поддержки функторов требуется немного более длинная реализация, все еще не слишком сложная:
// 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());
}