在之前的元编程系列文章中,讨论了折叠表达式。今天,将探索折叠表达式使用的极端情况。在开始之前,需要声明:本文中的代码示例展示了变参模板的使用,参数是通过值传递的,而不是转发。这样做是为了简化示例,专注于背后的思想。
在一元折叠(无初始化的折叠表达式)的情况下,对于三种类型的运算符是合法的:逻辑与(&&)、逻辑或(||)和逗号(,)。以下是每种运算符的示例。
在C++中,可以定义一个模板函数,它接受任意数量的参数,并使用逻辑与运算符进行折叠。如果调用时没有参数,函数将返回true。这背后的逻辑是,逻辑与运算符要求没有部分求值为false。在这种情况下,根本没有部分,因此没有部分求值为false,因此结果应该是true。
template<typename... Args>
auto and_cond(Args... args) {
return (args && ...);
}
对于逻辑或运算符,如果调用时没有参数,函数将返回false。这背后的逻辑是,逻辑或运算符要求至少有一个部分求值为true。因为没有部分,所以没有部分求值为true,因此结果是false。
template<typename... Args>
auto or_cond(Args... args) {
return (args || ...);
}
对于逗号运算符,如果没有传递参数,结果将是void()。这是因为逗号运算符用于分隔表达式,如果没有参数,就没有表达式可以分隔。
template<typename... Args>
auto comma_op(Args... args) {
return (args , ...);
}
对于其他运算符,如果没有参数的调用将不会编译。
这里的情况有点出乎意料。当只向变参包传递一个参数时,结果将忽略运算符,并返回发送到函数的相同类型和参数(在gcc 13.1.0和clang 16.0.0上测试过)。例如:
template<typename... Args>
auto func(Args... args) {
return (args || ...);
}
在main函数中调用:
int main() {
using namespace std::string_literals;
std::cout << func("I am a string"s);
// 将打印 "I am a string"
return EXIT_SUCCESS;
}
根据C++ Insights生成的代码是:
template<>
std::basic_string<char>
func<std::basic_string<char>>
(
std::basic_string<char> __args0
) {
return std::basic_string<char>(
static_cast<std::basic_string<char>&&>(__args0));
}
对于任何其他运算符,包括+=、*和->,也是如此。
对于二元折叠表达式,如果没有参数,将应用与单个参数的一元折叠表达式相同的规则:
template<typename... Args>
auto func(Args... args) {
return (args ->* ... ->* "I am a string"s);
}
在main函数中调用:
int main() {
std::cout << func();
// 将打印 "I am a string"
return EXIT_SUCCESS;
}
根据C++ Insights生成的代码是:
template<>
std::basic_string<char>
func<>
() {
return std::operator "" s(
"I am a string", 13UL);
}