参数包
或许会经常看到 ——
C++
template<typename... Ts>
void Foo(Ts... args)
{
}类似这样的写法,表示 Ts 是一个参数类型包,重点是类型 —— 比如调用了 Foo(1, 1.5, "Hello World"),那么 Ts 在概念上可以理解为 Ts = <int, double, const char*>
也就是生成的函数签名可以类似于 ——
C++
void Foo(int arg1, double arg2, const char* arg3);接着在使用的时候,也是利用 args... 进行参数展开,比如常见的引用转发
C++
template<typename... Args>
void Call(Args&&... args)
{
foo(std::forward<Args>(args)...);
}展开后大体上可以视作 ——
C++
foo(
std::forward<int>(1),
std::forward<double>(1.5)
std::forward<const char*>("Hello World"),
);在 17 之后的标准加入了折叠表达式,经典例子 ——
C++
template<typename... Args>
auto Sum(Args... args)
{
return (args + ...);
}大体上可以 ——
C++
Sum(1, 2, 3, 4);
1 + (2 + (3 + 4))其意思也非常的明确,取出第一个 arg,展开成 arg + () 的形式,然后递归调用 ... 进行一样的规则展开
看一个简单的泛型 EventBus 实现 ——
C++
template <class... Es>
class EventBus {
template <class E>
using Handlers = std::vector<std::function<void(const E &)>>;
std::tuple<Handlers<Es>...> table;
public:
template <class E>
requires(std::same_as<E, Es> || ...)
void subscribe(std::function<void(const E &)> h) {
std::get<Handlers<E>>(table).push_back(std::move(h));
}
template <class E>
requires(std::same_as<E, Es> || ...)
void publish(const E &e) {
for (auto &h : std::get<Handlers<E>>(table))
h(e);
}
};
using Bus = EventBus<EnemyKilled, PlayerHurt, PlayerDied, BulletFired>;先看 std::tuple<Handlers<Es>...> table;,先看 ... 前的表达式 —— Handler<Es>
也就是说对于一个类型,我们使用 Handler 做一个 Wrapper。继续看 ...,说明 Es 中的所有类型按照这个模式进行展开,所以最底下的 using 语句会使得上面的元组展开成 ——
C++
std::tuple<
std::vector<std::function<void(const EnemyKilled&)>>,
std::vector<std::function<void(const PlayerHurt&)>>,
std::vector<std::function<void(const PlayerDied&)>>,
std::vector<std::function<void(const BulletFired&)>>
> table;(把别名也进行展开),接着继续看 requires(std::same_as<E, Es> || ...),此处意思也非常明确,对于一个传入的类型 E,我们要求其和 Es 类型一致(先不看 ...)然后对于 Es 中的所有类型,我们都做这样的展开,并且中间使用或连接 —— 表达 E 类型要求必须是 Es 类型中的某一种
也可以封装成一个 concept
C++
template<class T, class... Ts>
concept OneOf = (std::same_as<T, Ts> || ...);
template<class E> requires OneOf<E, Es...>
void publish(const E& e);