【问题标题】:How do I determine the number of parameters of a std::function?如何确定 std::function 的参数数量?
【发布时间】:2013-04-04 08:38:58
【问题描述】:

我有以下问题。假设您想编写一个可以采用 lambda 表达式的通用函数。我知道如果参数的类型是 std::function,那么我不仅可以使用 lambda,还可以使用函数,甚至可以使用指向函数的指针。所以第一步,我做了以下事情:

void print(std::function<void(int, int)> fn) {
  fn(1,2);
}

int main() {

  print([](int i, int j) { std::cout << j <<','<<i<<'\n'; });
  return 0;
}

现在的问题是我想让这个函数通用,这意味着我不希望 lambda 表达式只有两个参数。 所以我尝试将 print 函数的签名更改为更通用的东西,例如:

template <class function_type>
void print(function_type fn);

但现在的问题是该函数接受任何对象,我对此并不满意。 但主要问题是,我不知道对象 fn 可以接受多少个参数。

因此,在某种程度上,我正在寻找一种编译时方法来确定 fn 有多少个参数,并在可能的情况下将 fn 的类型更改为 std::function。然后,鉴于我知道 fn 接受的参数数量,是否有一种通用方法可以打包任意数量的参数以传递给 fn?我什至不知道这在 C++11 中是否可行。我的意思是,考虑到参数的数量,有没有办法打包参数以传递给 fn?所以如果有两个参数,那么我会调用

fn(arg1, arg2);

如果有三个:

fn(arg1, arg2, arg3);

等等。

感谢大家的见解。

【问题讨论】:

  • 你希望如何为不同的函数实现这个?
  • 你要为参数传递什么值?
  • 我将只传递整数类型,并且都一样。但是该类是由参数数量模板化的。
  • 模板化了什么类?您的问题中没有课程。
  • 因为我简化了代码来解释问题。我所拥有的是一个可以通过传递函子或 lambda 表达式来构造的对象。例如,这个对象是由一个维度模板化的,所以如果它是一维的,那么我必须只用一个参数调用函子,如果它是二维的,有两个参数等等......

标签: c++ c++11 lambda template-meta-programming generic-programming


【解决方案1】:

以下 sn-ps 可能有用。

这给出了 std::function 采用的参数数量

template <typename Signature>
struct count_args;

template <typename Ret, typename... Args>
struct count_args<std::function<Ret(Args...)>> {
    static constexpr size_t value = sizeof...(Args);
};

例如以下代码编译(clang 3.2、gcc 4.7.2 和 icc 13.1.0)

static_assert(count_args<std::function<void()        >>::value == 0, "Ops!");
static_assert(count_args<std::function<void(int)     >>::value == 1, "Ops!");
static_assert(count_args<std::function<void(int, int)>>::value == 2, "Ops!");

据我了解,您想调用传递正确数量参数的函数对象,对吗?然后对于每个参数,我们需要提供一个可转换为其类型的值。具有这种普遍性的解决方案非常困难(甚至是不可能的)。因此,我将提出两种选择。

1 每个参数都是其类型的值初始化对象。 (这就是ecatmursuggested。)

template <typename Ret, typename... Args>
Ret call(const std::function<Ret(Args...)>& f) {
    return f(Args{}...); // for the intel compiler replace {} with ()
}

2 给定一个固定值,所有参数都从这个值隐式初始化:

template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<sizeof...(Args) == sizeof...(Vals), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val&, const Vals&... vals) {
    return f(vals...);
}

template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<(sizeof...(Args) > sizeof...(Vals)), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val& val, const Vals&... vals) {
    return call(f, val, val, vals...);
}

这三个重载是明确的,可以使用如下示例所示:

{
    std::function<char()> f = []() -> char {
        std::cout << "f() ";
        return 'A';
    };
    std::cout << call(f)    << std::endl; // calls f()
    std::cout << call(f, 0) << std::endl; // calls f()
}
{
    std::function<char(int)> f = [](int i) -> char {
        std::cout << "f(" << i << ") ";
        return 'B';
    };
    std::cout << call(f)    << std::endl; // calls f(0)
    std::cout << call(f, 1) << std::endl; // calls f(1)
}
{
    std::function<char(int, int)> f = [](int i, int j) -> char {
        std::cout << "f(" << i << "," << j << ") ";
        return 'C';
    };
    std::cout << call(f)    << std::endl; // calls f(0, 0)
    std::cout << call(f, 2) << std::endl; // calls f(2, 2)
}

【讨论】:

    【解决方案2】:

    是的,您可以使用可变参数模板将任意数量的参数打包到fn

    template <class function_type, class... Args>
    void print(function_type fn, Args... args)
    {
        //Call fn with args
        fn(std::forward<Args>(args...));
    }
    

    要查看参数包中有多少个args,可以使用sizeof...(args)

    【讨论】:

    • 您好托尼,感谢您的回答。然而, print 函数接受一个参数(函子或 lambda 表达式)。我需要的是一种方法来确定我可以调用多少个参数 fn。这甚至可能吗?例如,假设 print 是一个类的成员函数,并且根据 fn 接受的参数数量,该类将使用该数量的参数调用 fn。
    【解决方案3】:

    要确定可调用的签名,您可以使用Inferring the call signature of a lambda or arbitrary callable for "make_function" 中的解决方案。然后可以将 callable 打包成std::function,或者创建标签并使用参数推断:

    template<typename T> struct tag {};
    
    template<typename F, typename... Args>
    void print_impl(F &&fn, tag<void(Args...)>) {
      fn(Args{}...);
    }
    
    template<typename F>
    void print(F &&fn) {
      print_impl(std::forward<F>(fn), tag<get_signature<F>>{});
    }
    

    注意这使用了值初始化的参数;如果你想要更复杂的东西,你可以构建一个std::tuple&lt;Args...&gt; 并传递它,按照"unpacking" a tuple to call a matching function pointer 调用它。

    【讨论】:

    • 您好 ecatmur,我在理解该代码时遇到了问题。鉴于我知道函子的签名,我仍然需要在编译时知道该签名的参数数量才能触发对函子的正确函数调用,对吗?或者我错过了一些基本的东西?我需要的是:让我们有一个可以通过传递函子或 lambda 表达式来构造的对象。例如,这个对象是由一个维度模板化的,所以如果它是一维的,那么我必须只用一个参数调用函子,如果它是二维的,有两个参数,依此类推。
    • @AlejandroMarcosAragon 不,可以从计算的签名中推断出参数的数量。
    猜你喜欢
    • 2012-02-21
    • 1970-01-01
    • 2014-05-19
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 2015-12-25
    • 2016-12-25
    相关资源
    最近更新 更多