具有模板函数名称的可变参数模板
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<void (BaseSensor::*)(), sizeof...(SensorType)>&&
无论如何,请注意以下内容。 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<SensorType, &SensorType::update>...
我们以双重方式解压 SensorType
包 - 我们选择 SensorType
本身让 Method
知道成员函数属于谁,然后我们将指针传递给方法...如果我们可以使用 c++17,我们就不必将传感器类型传递给Method
,因为我们可以在那里使用auto template parameters...以上是关于具有模板函数名称的可变参数模板的主要内容,如果未能解决你的问题,请参考以下文章