【问题标题】:How can I generate as many parameters as is the arity of a function?如何生成与函数的数量一样多的参数?
【发布时间】:2014-05-14 08:32:14
【问题描述】:

假设我有以下函数,它接受一个函数作为参数。

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);
}

就像foobar 一样,我有几个函数返回T,并接受一些T 类型的参数。我希望test_func 生成对我的 RNG 的尽可能多的调用,因为函数 f 需要参数。换句话说,我们可以假设T 始终是整数类型,并且每个参数都是相同的,即对 RNG 的函数调用。

使用function_traits(例如the ones in Boost),我可以获取F的返回类型,这有点帮助。大致来说,我的问题是

如何生成所需数量的函数调用,使其与函数 F 的数量相匹配?

在 C++11 之前,我会研究 Boost.Preprocessor,或者可能依赖于模板专业化。现在有更好的方法吗?

【问题讨论】:

  • 好吧,数量确实是个问题。如果您将 bar 传递给它,其他参数应该是什么?除了您能够选择正确的值之外,其他人应该如何选择?编译器肯定不能。一种解决方法是将调用包装在 lambda 或其他知道剩余参数的函数中。
  • 您可以使用可变参数模板并转发参数包。
  • @Samuel 为简单起见,我们可以假设每个参数都是对(比如说)RNG 的调用,就像在示例中一样。此外,假设T 始终是整数类型。
  • @πάνταῥεῖ 这将如何运作? test_func 无法打包参数,因为它们被明确定义为 (int, int) 转发它们不会为具有任意参数的函数产生正确数量的参数。
  • 就像我通常做的那样,我只想提一下,C++ 中的可调用对象不一定具有明确定义的数量。

标签: c++ c++11


【解决方案1】:

首先定义一个名为 arity 的元函数来计算函数的数量(这只是一个简单的实现;也可以改进为计算函子的数量。请参阅我的答案 here。):

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!

然后是函数调用者:

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);
}

Here is a sample demo.

希望对您有所帮助。

【讨论】:

  • 您知道您对arg_evaluator 的调用具有未指定的评估顺序吗?
  • @Xeo:是的。 :-)。与问题中的相同。
  • @Xeo:因为在问题中,OP 使用的是“随机”生成器。你调用它的顺序真的很重要吗?
  • 是的:不同的运行可能会以不同的顺序进行。没有它,固定种子会产生可预测的参数,有利于重现测试错误运行。
  • @Yakk:它会以许多其他方式失败。问题是:是否需要这样做?我在问题中看不到任何此类要求。无论如何,“未指定的评估顺序”问题是固定的。
【解决方案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());
}

【讨论】:

  • 很好的解决方案。但请注意,它需要更改函数签名!
  • @Nawaz 只有模板参数不同。它很少有关系。如果需要,您可以随时将其包装在 template &lt;typename F&gt; void correct_func(F f) 中。
  • 我的意思是,如果调用者使用仿函数而不是函数,这是行不通的。我的(以当前形式)也不起作用。但正如我在答案本身中所说,这一切都需要arity 元函数的更复杂的实现。其他一切都将保持不变。我还提供了指向我的其他答案的链接,该链接提供了一些关于如何实现 arity 以使用仿函数的想法。
  • 小心点。你成为了未指定的参数评估顺序的牺牲品。
  • @R.MartinhoFernandes 不知道在哪里,你能指出界限吗?
猜你喜欢
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 2017-04-20
  • 2011-08-25
  • 2020-09-16
相关资源
最近更新 更多