C 和 C++ 中的可变参数编程

最后更新: 2022-09-05 18:24:42 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

C++ 可变参数模板保留每个参数的类型和数量。C 省略号函数会擦除这些信息,从而将类型解释和参数数量约束转移给调用者。对于新的类型化接口,优先使用 C++ 参数包。

参数包、展开与折叠 #

模板参数包和函数参数包可以包含零个或多个元素。参数包展开会针对每个元素应用一次模式。

cpp
#include <utility>

template <class Function, class... Ts>
decltype(auto) invoke(Function&& function, Ts&&... values) {
    return std::forward<Function>(function)(std::forward<Ts>(values)...);
}

template <class... Ts>
auto sum(Ts... values) {
    return (0 + ... + values); // binary left fold; 0 is the empty-pack identity
}

std::forward 保留为每个转发引用推导出的值类别。它不会延长参数的生命周期,因此被调用方仍必须遵守常规的引用和所有权约定。

展开可以出现在模板参数列表、函数实参、基类说明符列表、初始化器、lambda 捕获以及其他由语法定义的上下文中。嵌套展开或同时展开要求参数包具有兼容的长度。

一元折叠(例如 (values + ...))要求参数包非空,除非运算符具有语言定义的空标识值。当空输入具有实际意义时,使用二元折叠提供显式标识值;当空输入没有意义时,使用约束拒绝空输入。

cpp
template <class... Ts>
requires (sizeof...(Ts) > 0)
auto product(Ts... values) {
    return (... * values);
}

C 可变参数函数 #

使用 ... 声明的 C 函数通过 va_list、va_start、va_arg、va_copy 和 va_end 读取未命名参数。声明既不提供数量元数据,也不提供类型元数据。因此,计数参数、格式字符串或文档规定的哨兵值属于 API 契约的一部分。

c
#include <stddef.h>
#include <stdarg.h>

int sum_ints(size_t count, ...) {
    va_list arguments;
    va_start(arguments, count);
    int result = 0;
    for (size_t i = 0; i < count; ++i)
        result += va_arg(arguments, int);
    va_end(arguments);
    return result;
}

默认参数提升会应用于 ...:float 会变为 double,而窄于 int 的整数类型会提升为 int 或 unsigned int。va_arg 必须请求该位置所期望的、经过提升且兼容的类型。读取错误的类型、省略 va_end、读取超出所提供参数范围的内容,或依赖不存在的哨兵值,都会产生未定义行为。仅在 ABI 兼容性要求,或对于格式化等已经规定了精确约定的接口中使用 C 可变参数。

评论

在 GitHub 上阅读或评论(发表评论需要登录)