具有模板函数名称的可变参数模板

Posted

技术标签:

【中文标题】具有模板函数名称的可变参数模板【英文标题】:variadic templates with template function names 【发布时间】:2016-09-18 14:51:15 【问题描述】:

在this 问题之后,我试图避免复制粘贴一些与调用BaseSensor 类的所有同名方法相关的代码。

在sensor.hpp中

struct EdgeSensor //a mixin

    void update()
    void printStats() 
;

struct TrendSensor //another mixin

    void update()
    void printStats() 
;

template<typename ... SensorType>
class BaseSensor : public SensorType ... //to my BaseSensor class

    void update() /* what goes in here??? */
    void printStats() /* what goes in here??? */
;

在sensor.t.hpp中

template<typename ... SensorType>
void BaseSensor<SensorType...>::update()

    int arr[] =  (SensorType::update(), 0)..., 0 ;
    (void)arr;


template<typename ... SensorType>
void BaseSensor<SensorType...>::printStats()

    int arr[] =  (SensorType::printStats(), 0)..., 0 ;
    (void)arr;

在 main.cpp 中

int main(int , const char **) 

    
        BaseSensor<EdgeSensor,TrendSensor> ets;
        ets.update();
        ets.printStats();
    
    
        BaseSensor<EdgeSensor> ets;
        ets.update();
        ets.printStats();
    

上面的代码依次执行所有mixin中的update(),然后继续执行所有mixin中的所有printStats()

我想知道是否有可能避免重复执行 BaseSensor::update()BaseSensor::printStats() 并创建一个通用(模板)函数,该函数接受目标函数的名称以在所有 mixins 中执行:

例如,我可以创建一个方法runAll()

template<typename ... SensorType>
class BaseSensor : public SensorType ... //to my BaseSensor class

    void update() /* what goes in here??? */
    void printStats() /* what goes in here??? */

    template<typename FnName>
    void runAll(FnName f)
    
        int arr[] =  (SensorType::f(), 0)..., 0 ;
        (void)arr;
    
;

我将如何从BaseSensor::update()BaseSensor::printStats() 调用它。我尝试过使用

void update()  runAll<update>(); 
void printStats()  runAll<printStats>(); 

但这不起作用(没想到会这样)。将函数名称作为函数参数传递的问题(我看到许多其他问题,例如here 是我不知道如何从BaseSensor::update() 指向各种::update() 函数。例如

void update()  runAll<update>( update() ); 

也不正确。

在这种情况下是否可以避免复制?如果我将工作中的runAll() 移动到文件“sensor.t.hpp”中,模板参数会是什么样子?

谢谢

【问题讨论】:

【参考方案1】:

您可以使用通用 lambda 和一种控制反转。 它遵循一个最小的工作示例:

#include<iostream>

struct EdgeSensor

    void update()  std::cout << "EdgeSensor::update" << std::endl; 
    void printStats()  std::cout << "EdgeSensor::printStats" << std::endl; 
;

struct TrendSensor

    void update()  std::cout << "TrendSensor::update" << std::endl; 
    void printStats()  std::cout << "TrendSensor::printStats" << std::endl; 
;

template<typename ... SensorType>
class BaseSensor : public SensorType ...

    template<typename F>
    void execute(F &&f) 
        int arr[] =  (f(static_cast<SensorType&>(*this)), 0)..., 0 ;
        (void)arr;
    

public:
    void update() 
        execute([](auto &t)  t.update(); );
    

    void printStats() 
        execute([](auto &t)  t.printStats(); );
    
;

int main() 
    BaseSensor<EdgeSensor,TrendSensor> ets;
    ets.update();
    ets.printStats();

【讨论】:

lambda 参数应该是一个引用。 我看不出为什么普通参考不起作用。 static_cast 为您提供参考。 @SamVarshavchik 哦,对不起,我刚刚重新阅读了评论。你说的对。你说lambda 参数。我误读了评论。感谢您指出这一点。 您好,您能否在详细说明上述升技时纠正我?你基本上运行一个 lambda fn 运行(或准备运行?)t.update()。那么“this”指向 lambda 函数?并且 lambda 的实际执行发生在数组初始化期间? @nass 我想我不明白这个问题。无论如何,我们的想法是传递一个 lambda 作为参数,并让 executor 以正确的类型调用该函数,这样您就不必关心这些类型有多少和是什么。 【参考方案2】:

如果你可以使用 c++1z 折叠表达式可能是最短的方法:

template<typename ... SensorType>
class BaseSensor : public SensorType ... //to my BaseSensor class

public:
    void update()  (SensorType::update(),...); 
    void printStats()  (SensorType::printStats(),...); 
;

【讨论】:

折叠表情尚未加入游戏。无论如何 +1 提到他们。 这似乎无法在 g++-std=c++11 上运行,我还不能使用更新的标准。 @nass 是的,至少在第 6 版中确实需要 -std=c++1z 和 gcc【参考方案3】:

另一种c++11 方式可能是使用std::array 指向方法的指针,例如:

template<typename ... SensorType>
class BaseSensor : public SensorType ... //to my BaseSensor class

   void runAll(std::array<void (BaseSensor::*)(), sizeof...(SensorType)>&& vs) 
      for (auto v: vs) 
         (this->*v)();
      
   

public:
    void update() 
       runAll(&SensorType::update...);
    
    void printStats() 
       runAll(&SensorType::printStats...);
    
;

【讨论】:

其实这很漂亮:) 不错,但我会使用std::array,因为它的大小是已知的并且不需要分配任何东西:std::array&lt;void (BaseSensor::*)(), sizeof...(SensorType)&gt;&amp;&amp; 无论如何,请注意以下内容。 Here 是使用 lambda 的版本,here 是使用仿函数的 C++11,here 是此答案中的版本。查看生成的代码。你注意到什么?这个版本,即使经过优化,也要贵得多。确实很好奇,但值得一提。 ;-) @W.F.如果您在 Godbolt 上尝试它们,您会发现生成的代码几乎与使用 lambda 或仿函数(当然,一旦优化)相同。实际上非常不可思议。 Here 是。 我错过了优化 :) 想知道为什么数组版本如此庞大... lambdas 的另一个专业人士:)【参考方案4】:

这是另一种更紧凑的解决方案,可在 C++11 中编译:

#include <type_traits>
#include <iostream>

struct EdgeSensor 
    void update()  std::cout << "EdgeSensor::update" << std::endl; 
    void printStats()  std::cout << "EdgeSensor::printStats" << std::endl; 
;

struct TrendSensor 
    void update()  std::cout << "TrendSensor::update" << std::endl; 
    void printStats()  std::cout << "TrendSensor::printStats" << std::endl; 
;

template<typename ... SensorType>
class BaseSensor : public SensorType ...  
    template <void(SensorType::* ...M)()>
    void run() 
        int arr[] =  0, ((this->*M)(), 0)... ;
        (void)arr;
    

public:   
    void update()    
        run<&SensorType::update...>();
    

    void printStats() 
        run<&SensorType::printStats...>();
    
;

int main() 
    BaseSensor<EdgeSensor, TrendSensor> bs;
    bs.update();
    bs.printStats();

我很想说您不需要任何结构来支持指向成员方法的指针包。 不管怎样,我找到了this compiles with clang and it doesn't work with GCC。我仍在尝试确定代码是否格式错误或问题出在编译器中。

我建议您关注the other question 以了解您是否可以使用此代码。

【讨论】:

【参考方案5】:

我想到了另一个纯 c++11 答案。这个使用标签调度和非类型模板参数:

template <class T, void (T::*)()>
struct Method  ;

template<typename ... SensorType>
class BaseSensor : public SensorType ... //to my BaseSensor class

   template <class T, void(T::*M)()>
   int runSingle(Method<T, M>) 
      (this->*M)();
      return 0;
   

   template <class... Ts>
   void runAll() 
      int run[sizeof...(Ts)] =  runSingle(Ts)... ;
      (void)run;
   

public:
    void update() 
       runAll<Method<SensorType, &SensorType::update>...>();
    
    void printStats() 
       runAll<Method<SensorType, &SensorType::printStats>...>();
    
;

必须说明的是,除了折叠表达式(包括 skypjack 的)之外,这些答案都不会处理 mixin 类的虚拟被调用者方法......但是我认为可以轻松修改 skypjack 答案以实现这样一个效果:

#include<type_traits>

// (...)

template<typename ... SensorType>
class BaseSensor : public SensorType ...

    template<typename F>
    void execute(F &&f) 
        int arr[] =  (f(static_cast<SensorType&>(*this)), 0)..., 0 ;
        (void)arr;
    

public:
    void update() 
        execute([](auto &t)  t.std::remove_reference<decltype(t)>::type::update(); );
    

    void printStats() 
        execute([](auto &t)  t.std::remove_reference<decltype(t)>::type::printStats(); );
    
;

【讨论】:

您好,我们可以看看您的解决方案吗? Method 到底在做什么?我该如何解释它?我有点困惑:) 当然Method是一种容器类型,用来承载单个成员函数的信息……它不存储任何信息,有点类似于tag dispatching中的标签跨度> 但是解决方案不是标签调度,因为它不会通过参数将编译时信息传递给函数 (runAll),而是通过模板参数... Method 执行有关SensorType 的信息以及指向成员函数的指针。这可以通过模板来完成,除了普通的旧类型之外,模板还可以包含满足conditions 的非类型参数(它们通常需要静态可访问) by Method&lt;SensorType, &amp;SensorType::update&gt;... 我们以双重方式解压 SensorType 包 - 我们选择 SensorType 本身让 Method 知道成员函数属于谁,然后我们将指针传递给方法...如果我们可以使用 c++17,我们就不必将传感器类型传递给Method,因为我们可以在那里使用auto template parameters...

以上是关于具有模板函数名称的可变参数模板的主要内容,如果未能解决你的问题,请参考以下文章

可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?

匹配任何类型参数的 C++ 可变参数模板模板参数

C++11 ——— 可变参数模板

C++11 ——— 可变参数模板

如何将构造函数(可变参数)作为模板参数传递?

第20课 可变参数模板_模板参数包和函数参数包