【问题标题】:Use variadic function as template argument使用可变参数函数作为模板参数
【发布时间】:2018-10-21 04:01:26
【问题描述】:

我想让以下代码在不更改 Child1Child2 类的情况下运行:

#include <iostream>

int triple(int a) {
    return a * 3;
}

int add(int a, int b) {
    return a + b;
}

template<int (*F)(int)>
class Parent {
    public:
        Parent(int a) {
            std::cout << "constructed: " << F(a) << std::endl;
        }
};

class Child1 : Parent<triple> {
    public:
        Child1(int a) : Parent(a) {}
};

/*class Child2 : Parent<add> {
    public:
        Child2(int a, int b) : Parent(a, b) {}
};*/

int main() {
    Child1 child(4);
    //Child2 child(5, 6);
    return 0;
}

例如,您可以看到,Child1 继承自 Parent,该 Parent 已使用 triple 函数实例化。因此,当Child1 被实例化为 4 时,它会输出“constructed: 12”。

相比之下,Child2 被注释掉了,因为它显然还不能工作。在主函数中,我试图将两个参数传递给Child2 构造函数,就像底层add() 函数所期望的那样。然而,Parent 的构造函数只接受一个参数,并且可能需要在它前面加上template&lt;typename Args...&gt; 才能解决问题。此外,Parent 类将需要一个模板参数,如int (*F)(Args...)。最终,像 main 函数一样构造一个Child2 实例应该输出“constructed: 11”。

我怎样才能做到这一点,即创建一个模板参数,它是一个可以有任意数量参数的函数?同样,请注意 Parent 类的代码是唯一可以更改的内容。

【问题讨论】:

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


    【解决方案1】:

    使用 C++17,您可以使用推导的非类型模板参数并使构造函数成为可变参数模板:

    template<auto x_pointer_to_function>
    class Parent
    {
        public:
        template<typename... x_Args>
        Parent(x_Args &&... args)
        {
            std::cout << "constructed: " << ((*x_pointer_to_function)(::std::forward<x_Args>(args)...)) << std::endl;
        }
    };
    

    online compiler

    【讨论】:

    • redundant ((* 你已经到了那里。
    • 这样定义的、不受约束的构造函数在复制构造函数中胜出
    • @sigalor 潜在的问题是Parent&lt;&amp;add&gt; p1{1, 2}; Parent&lt;&amp;add&gt; p2{p1}; 将尝试调用此构造函数而不是复制构造函数并失败。虽然这应该不是什么大问题,因为看起来 Parent 不应该单独使用,Child 副本仍然可以按预期工作。
    • @sigalor 返回值(和其他东西)可以用::std::is_invocable进行约束
    • @AlexanderHuszagh 通话并不含糊。此构造函数获胜,因为正在复制的非 const 左值表达式不需要 const 限定。否则非模板函数优先
    【解决方案2】:

    来不及玩了?

    我提出了基于 C++17 VTT 的 auto 的变体,它使用类专业化来提取 Args... 输入类型(以及 Ret 类型,如果有用的话)。

    我是说

    template <auto>
    class Parent;
    
    template <typename Ret, typename ... Args, Ret(*F)(Args...)>
    class Parent<F>
     {
       public:
          Parent (Args const & ... as)
           { std::cout << "constructed: " << F(as...) << std::endl; }
     };
    

    以下是完整的编译示例

    #include <iostream>
    
    int triple (int a)
     { return a * 3; }
    
    long add (int a, int b)
     { return a + b; }
    
    template <auto>
    class Parent;
    
    template <typename Ret, typename ... Args, Ret(*F)(Args...)>
    class Parent<F>
     {
       public:
          Parent (Args const & ... as)
           { std::cout << "constructed: " << F(as...) << std::endl; }
     };
    
    class Child1 : public Parent<triple>
     {
        public:
            Child1 (int a) : Parent{a}
             { }
     };
    
    class Child2 : public Parent<add>
     {
        public:
            Child2 (int a, int b) : Parent{a, b}
             { }
     };
    
    int main()
     {
       Child1 c1{4};
       Child2 c2{5, 6};
     }
    

    松散的完美转发,但可以控制参数的数量(和类型)。

    【讨论】:

    • 该死,这看起来也很棒,尤其是因为自定义(并且可能是可约束的)返回值(使用 enable_if)!关于另一个答案的复制构造函数讨论,你能告诉我你的行为是否有任何不同和/或复制可能有什么问题吗?
    • @sigalor - 是的,我想你可以 SFINAE 对返回类型强加一些东西;关于复制构造函数的讨论……嗯……我不明白;我的意思是......如果你想在构造函数中执行模板函数F,将构造函数的参数传递给它,你需要一个std::tuple&lt;Args...&gt;作为成员将第一个对象的参数传递给第二个对象。
    • 好吧,我明白了 :) 但是,请原谅我将保留已接受的答案,因为它是我的问题中最简约的解决方案。
    • @sigalor - 完全没有问题:VTT 的答案是一个很好的答案(我已经赞成)。
    猜你喜欢
    • 2014-10-24
    • 2011-08-26
    • 2021-10-01
    • 1970-01-01
    • 2016-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多