Skip to content

参数包 ​

或许会经常看到 ——

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);

Released under the MIT License.