【问题标题】:Allow function of variadic argument count as template function argument允许可变参数的函数作为模板函数参数
【发布时间】:2020-10-07 12:53:58
【问题描述】:

在 C++ 中,我们可以很容易地定义模板函数参数,如this 问题所示。但是,这只允许具有已定义参数列表和返回值的函数。

我们可以像这样将这些类型作为模板参数:

template<typename R, typename P, R(*Func)(P)>
R proxy_func(P arg)
{
    // do some stuff...
    R&& res{ Func(std::forward(arg)) };
    // do some more stuff...
    return res;
}

但是,这里我们仍然限制了固定数量的参数。

像下面这样的尝试不会编译(至少在 MSVC 19.24.28315 上),因为 "模板参数 'Func' 不能使用,因为它遵循模板参数包并且不能从函数参数中推导出来'proxy_func'".

template<typename R, typename... P, R(*Func)(P...)>
R proxy_func(P... args)
{
    // do some stuff...
    R&& res{ Func(std::forward(args)...) };
    // do some more stuff...
    return res;
}

那么,允许任意函数作为模板参数的选项有哪些?同样重要的是 - 允许其 parameters 用作参数,例如,它使用的函数模板(例如在上面的proxy_func)?理想情况下,我可以使用任何函数调用proxy_func 作为(理想情况下单个)模板参数Func,并能够将其(proxy_func's)参数应用于Func .


编辑:我对将函数-instance 作为模板参数特别感兴趣(例如,允许 proxy_func 的模板实例化基于个人Funcs)。

【问题讨论】:

  • 编译良好here
  • 我认为后半部分已经由特定于编译器的函数内联优化处理。与往常一样,基准测试并阅读程序集。
  • @user202729 是的,我一点也不反对。然而,优化只是一个示例性的原因。我一直在寻找一个编译时的解决方案,不管实际的好处。

标签: c++ templates variadic-templates function-templates


【解决方案1】:

从某种意义上说,您链接的问答是一种复杂的方式。简单的方法是:

template <typename F>
void foo(F f) {
    f();    // call the function
}

在您的情况下,可以推断出返回值,并且可以从参数推断出参数类型到foo。请注意,它们不必与 f 的参数类型完全匹配,只要可以使用它们调用 f

template <typename F,typename ... Args>
auto foo( F f, Args ... args) {
    return f(args...);    // call the function
}

为了简洁起见,我省略了转发和临时存储呼叫结果,但答案仍然适用。

如果您想拥有函数本身,而不是将其类型作为模板参数(对不起,我首先错过了那部分),这仍然有效:

template <auto f,typename ... Args>
auto foo(Args ... args) {
    return f(args...);    // call the function
}

但是,如果 f 是 lambda,这将不起作用。详情见这里:https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter

【讨论】:

  • 这正是我想要的,谢谢!现在 - 几乎感觉要求太多 - 甚至可能有机会省略fooArgs 模板参数 并以某种方式基于模板函数指定参数包args f?
  • 请注意,最后一段代码仅适用于函数指针,并且仅适用于 C++ 17 (en.cppreference.com/w/cpp/language/…)(C++ 20 可能会更改)
  • @Reizo,您意识到您可以像这样使用@idclev 的答案:foo&lt;some_func&gt;(3,'a')。无需在调用站点显式列出参数类型。
  • @Reizo 哦,我错过了那条评论。我错误地假设很明显Args 是推导出来的,只有f 需要明确指定。基本上是埃利奥特所说的
  • @Reizo 我写了“为简洁起见,我省略了转发和存储...”。要完全正确,还需要更多:youtube.com/watch?v=I3T4lePH-yA
【解决方案2】:

感谢 C++17,auto 可以作为非类型模板参数的占位符,然后我们可以在没有类型声明的情况下传递函数指针:

template<auto func, typename... Args>
auto proxy_func(Args&&... args){
    // do something...
    auto result = func(std::forward<Args>(args)...);
    // do something...
    return result;
}

而且在 C++17 之前非常累人。如果你真的想要,你必须使用包装器:

int foo(){ return 0; };

template<typename Ret, typename... Args>
struct func_wrapper{
    template<Ret(*func)(Args...)>
    struct helper{
        typedef Ret return_type;
        typedef std::tuple<Args...> parameter_types;
        static constexpr Ret (*value)(Args...) = func;
    };
};
template<typename Ret, typename... Args>
auto helper(Ret(*)(Args...))->func_wrapper<Ret, Args...>;

template<typename Wrapper, typename... Args>
auto proxy_func(Args&&... args)
->decltype(Wrapper::value(std::forward<Args>(args)...)){
    // do something...
    auto result = Wrapper::value(std::forward<Args>(args)...);
    // do something...
    return result;
}

int main(){
    typedef decltype(helper(&foo)) wrapper_klass;
    typedef wrapper_klass::helper<&foo> wrapper_type;
    proxy_func<wrapper_type>();
}

然后,如果您想通过func 决定Args,您必须使用一些棘手的方法来解压缩参数包:

template<auto>
struct proxy_func_klass;
template<typename Ret, typename... Args, Ret(*func)(Args...)>
struct proxy_func_klass<func>{
    static Ret call(Args... args){
        return func(args...);
    }
};

和 C++11 版本:

int foo(){ return 0; };

template<typename Ret, typename... Args>
struct func_wrapper{
    template<Ret(*func)(Args...)>
    struct helper{
        typedef Ret return_type;
        typedef std::tuple<Args...> parameter_types;
        static constexpr Ret (*value)(Args...) = func;
    };
};
template<typename Ret, typename... Args>
auto helper(Ret(*)(Args...))->func_wrapper<Ret, Args...>;

template<typename, typename, typename>
struct helper_klass;
template<typename Self, typename Ret, typename... Args>
struct helper_klass<Self, Ret, std::tuple<Args...>>{
    static Ret call(Args... args){
        return Self::value(args...);
    }
};
template<typename Wrapper>
struct proxy_func_klass : public helper_klass<proxy_func_klass<Wrapper>, typename Wrapper::return_type, typename Wrapper::parameter_types>{
    static constexpr auto value = Wrapper::value;
};


int main(){
    typedef decltype(helper(&foo)) wrapper_klass;
    typedef wrapper_klass::helper<&foo> wrapper_type;
    proxy_func_klass<wrapper_type>::call();
}

【讨论】:

  • 相当完整的答案。您是否有机会选择适用于重载的选项? (即在编译时传递的函数被重载)
  • @Elliott 很遗憾,这是不可能的。如果我们要推导参数类型和返回类型,函数一定不能重载,否则无论如何都是模棱两可的。如果您从函数式编程和类型系统的角度考虑它会很有帮助。
  • 我不明白为什么它是模棱两可的。如果我有int Bar()char Bar(int),然后像这样使用代理功能:proxy_func&lt;Bar&gt;(3),那么信息就足够了。让编译器在这里处理重载,只要我们有正确的工具来正确地纠正proxy_func。你不同意吗?
  • @Elliott 如果您传递参数类型,它看起来就足够了。我说的模棱两可就是推导参数类型。但另一方面,我们不能使用在此位置后面声明的类型名称。我们不能声明 func 的类型取决于稍后声明的类型名称。如果你仍然想要,你必须先得到Args...,然后传递funcargs...,就像我在C++11中所做的那样。
  • 哦,我的错。即使我们之前得到了Args...,仍然是不可能的,因为在func被传递之前,Ret是无法推导出来的,这与Ret(*)(Args...)传递func冲突。但无论如何,我认为它在语义上是不正确的,因为 2 个重载的 Bar 有 2 个不同的实体,它们之间唯一相同的是它们的名称,现在不能在 C++ 中传递。
猜你喜欢
  • 2011-08-26
  • 1970-01-01
  • 1970-01-01
  • 2016-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-10-24
  • 2020-04-20
相关资源
最近更新 更多