c++17特性
C++17特性
类模板的模板参数推导
类模板通过构造函数的参数类型自动推导模板类型参数,而无需显式指定模板参数
#include <iostream>
template <typename T>
class MyClass {
public:
T value;
MyClass(T val) : value(val) {}
};
int main() {
MyClass obj(10); // 编译器推导 T 为 int
std::cout << obj.value << std::endl; // 输出: 10
}
使用 auto 声明非类型模板参数
非类型模板参数是模板参数中的一个特殊类型,它并不是类型(如 int
或 double
),而是一个常量值或常量表达式。这些值可以是整数、指针、引用、甚至是用户定义的类型实例
template <auto N>
auto N
会被推导为传入的值的类型,,如果传入 42
,N
的类型会推导为 int
;如果传入 "hello"
,N
会推导为 const char*
示例
#include <iostream>
#include <string>
template <auto N>
void print() {
std::cout << N << std::endl;
}
int main() {
print<42>(); // 自动推导 N 为 int
print<3.14>(); // 自动推导 N 为 double
print<'A'>(); // 自动推导 N 为 char
print<std::string("Hello")>(); // 自动推导 N 为 const char*
}
- 自动类型推导:通过
auto
,模板可以根据传入的值推导出参数的类型。 - 非类型参数的值:可以传递常量值、字符串字面量、甚至是常量表达式。
- 类型限制:非类型模板参数的类型推导受限于 C++ 标准,必须是常量或可以推导为常量的类型。
折叠表达式
折叠表达式的形式可以分为以下几种(其中 op
表示 C++ 中的二元运算符,如 +
, -
, *
, /
, &&
, ||
等):
- 一元折叠(Unary fold)
- 左折叠:
(... op pack)
- 右折叠:
(pack op ...)
- 左折叠:
- 二元折叠(Binary fold)
- 左折叠:
(init op ... op pack)
- 右折叠:
(pack op ... op init)
- 左折叠:
其中,参数包 pack
在展开时会将所有参数依次用运算符连接。例如,如果 pack
是 (a, b, c, d)
,运算符为 +
,那么:
(... + pack)
展开后相当于:(((a + b) + c) + d)
(pack + ...)
展开后相当于:(a + (b + (c + d)))
(init + ... + pack)
展开后相当于:(((init + a) + b) + c) + d
(pack + ... + init)
展开后相当于:(a + (b + (c + (d + init))))
[!WARNING]
⚠️ 对于大多数交换律成立的运算符(如
+
,*
),左折叠或右折叠结果相同;但如果运算符不满足结合律或交换律(如减法-
、除法/
、逗号运算符,
等),就需要根据实际需要来选择左折叠还是右折叠
示例
求和
传统写法
#include <iostream>
#include <initializer_list>
template <typename... Args>
auto sum(Args... args) {
// 利用 std::initializer_list 的构造来展开参数包
auto list = {args...};
int total = 0;
for (auto v : list) {
total += v;
}
return total;
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出:15
return 0;
}
使用折叠表达式
#include <iostream>
template <typename... Args>
auto fold_sum(Args... args) {
// 使用一元左折叠 (... + args)
return (args + ...);
}
int main() {
std::cout << fold_sum(1, 2, 3, 4, 5) << std::endl; // 输出:15
return 0;
}
(args + ...)
在参数包 (1, 2, 3, 4, 5)
上展开时,相当于 1 + (2 + (3 + (4 + 5)))
。
带初始值的折叠
例如:给一个字符串前面加上前缀,或者在数值计算中加一个初始值
#include <iostream>
template <typename... Args>
auto fold_sum_init(int init, Args... args) {
// 左折叠: (init + ... + args)
return (init + ... + args);
}
int main() {
std::cout << fold_sum_init(100, 1, 2, 3, 4, 5) << std::endl;
// 相当于: (((((100 + 1) + 2) + 3) + 4) + 5) = 115
return 0;
}
逻辑折叠
折叠表达式也常用于对多个布尔值进行逻辑运算。比如,想判断一组条件是否全部为真(逻辑与)或只要有一个条件为真(逻辑或)
#include <iostream>
template <typename... Bools>
bool all_true(Bools... bools) {
// 左折叠写法:(... && bools)
return (... && bools);
}
template <typename... Bools>
bool any_true(Bools... bools) {
// 左折叠写法:(... || bools)
return (... || bools);
}
int main() {
std::cout << std::boolalpha;
std::cout << "all_true(true, true, false): " << all_true(true, true, false) << std::endl;
std::cout << "any_true(true, true, false): " << any_true(true, true, false) << std::endl;
// 输出:
// all_true(true, true, false): false
// any_true(true, true, false): true
return 0;
}
-
(... && bools)
相当于true && (true && false)
。 -
(... || bools)
相当于true || (true || false)
。
其他运算符折叠
所有 C++ 中的二元运算符都可以与折叠表达式配合使用,包括逗号运算符、位运算符、比较运算符等
#include <iostream>
template <typename... Args>
void print_all(Args... args) {
// 逗号运算符折叠: (... , (std::cout << args << " "))
// 可以理解为:((std::cout << args1 << " "), (std::cout << args2 << " "), ...)
((std::cout << args << " "), ...);
std::cout << std::endl;
}
int main() {
print_all(1, 2.5, "hello", 'c');
// 输出:1 2.5 hello c
return 0;
}
((std::cout << args << " "), ...)
是一元右折叠,按照参数包的顺序依次输出元素
注意事项
- 折叠方向:
- 结合律不成立时,要谨慎选择使用左折叠还是右折叠。
- 对于运算符如
-
、/
等,左折叠和右折叠的计算结果往往不同
- 空参数包:
- 如果参数包展开时为空,折叠表达式会变得无效。例如
(args + ...)
在空参数包下没有定义意义,可能会导致编译错误。 - 如果需要处理空包,可以使用带初始值的二元折叠
(init + ... + args)
,这样当包为空时,表达式就退化为init
。
- 如果参数包展开时为空,折叠表达式会变得无效。例如
- 合法的二元运算符:
- 并非所有的符号都能用作折叠表达式中的
op
,必须是 C++ 中定义了的二元运算符,如+
,-
,*
,/
,%
,==
,!=
,&&
,||
,&
,|
,^
,<<
,>>
,,
等。
- 并非所有的符号都能用作折叠表达式中的
#include <iostream>
// 1. 不带初始值的折叠求和
template <typename... Args>
auto fold_sum(Args... args) {
return (args + ...);
}
// 2. 带初始值的折叠求和
template <typename... Args>
auto fold_sum_init(int init, Args... args) {
return (init + ... + args);
}
// 3. 逻辑折叠
template <typename... Bools>
bool all_true(Bools... bools) {
return (... && bools);
}
template <typename... Bools>
bool any_true(Bools... bools) {
return (... || bools);
}
// 4. 逗号运算符折叠
template <typename... Args>
void print_all(Args... args) {
((std::cout << args << " "), ...);
std::cout << std::endl;
}
int main() {
std::cout << "fold_sum(1,2,3,4,5): " << fold_sum(1,2,3,4,5) << std::endl;
std::cout << "fold_sum_init(100,1,2,3,4,5): " << fold_sum_init(100,1,2,3,4,5) << std::endl;
std::cout << std::boolalpha;
std::cout << "all_true(true,true,false): " << all_true(true, true, false) << std::endl;
std::cout << "any_true(true,true,false): " << any_true(true, true, false) << std::endl;
print_all(1, 2.5, "hello", 'c');
return 0;
}
constexpr lambda
允许在编译时执行lambda表达式
捕获的变量必须是constexpr
或常量表达式
注意
- Lambda的捕获列表必须为空(
[]
)。 - Lambda内部的代码只能调用其他
constexpr
函数或操作。 - Lambda返回值和所有逻辑都必须满足
constexpr
的要求。
constexpr auto lambda_name = [](/* 参数列表 */) constexpr -> 返回类型 {
// constexpr 函数体
};
示例
-
基础用法
#include <iostream> constexpr auto square = [](int x) constexpr { return x * x; }; constexpr int result = square(5); // 编译期计算 int main() { std::cout << "Square of 5 is " << result << '\n'; // 输出 25 return 0; }
-
使用
constexpr lambda
实现简单的编译期逻辑
#include <iostream>
// constexpr lambda for factorial
constexpr auto factorial = [](int n) constexpr {
int result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
};
constexpr int fact_5 = factorial(5); // 编译期计算
int main() {
std::cout << "Factorial of 5 is " << fact_5 << '\n'; // 输出 120
return 0;
}
-
配合
std::array
进行编译期数组初始化#include <array> #include <iostream> constexpr auto generate_fibonacci = [](size_t n) constexpr { std::array<int, 10> fib = {0, 1}; for (size_t i = 2; i < n; ++i) { fib[i] = fib[i - 1] + fib[i - 2]; } return fib; }; constexpr auto fib_array = generate_fibonacci(10); int main() { for (const auto& num : fib_array) { std::cout << num << " "; // 输出 0 1 1 2 3 5 8 13 21 34 } std::cout << '\n'; return 0; }
-
C++20结合模板与编译期推导
#include <iostream> #include <array> template <typename Lambda> constexpr auto apply_to_array(const std::array<int, 5>& arr, Lambda lambda) { std::array<int, 5> result{}; for (size_t i = 0; i < arr.size(); ++i) { result[i] = lambda(arr[i]); } return result; } constexpr auto square = [](int x) constexpr { return x * x; }; constexpr auto input_array = std::array<int, 5>{1, 2, 3, 4, 5}; constexpr auto squared_array = apply_to_array(input_array, square); int main() { for (const auto& val : squared_array) { std::cout << val << " "; // 输出 1 4 9 16 25 } std::cout << '\n'; return 0; }
Lambda按值捕获this指针
在c++11,Lambda捕获this是引用捕获,c++17添加了*this
的按值捕获
struct MyObj {
int value {123};
auto getValueCopy() {
return [*this] { return value; };
}
auto getValueRef() {
return [this] { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321;
valueCopy(); // 123
valueRef(); // 321
内联变量
inline
描述符可以用于变量与函数
在C++17之前,定义全局或静态变量,需要使用extern
进行声明,并且在一个源文件中进行定义,而使用inline
可以在头文件中进行定义,被多个源文件进行包含也不会导致重复定义
示例
-
定义全局变量
common.h
inline int a = 10;
test.cpp
#include "common.h" void test(){ a += 10; }
main.cpp
#include <iostream> #include "common.h" extern void test(); int main() { a = 10; test(); std::cout << "Value: " << a << std::endl; return 0; }
-
类静态变量
可以直接在声明类内定义一个静态成员变量,而不需要在源文件中进行初始化
#include <iostream> class MyClass { public: // 内联静态成员变量 inline static int staticValue = 10; }; int main() { std::cout << "Static Value: " << MyClass::staticValue << std::endl; // 修改静态变量 MyClass::staticValue = 20; std::cout << "Updated Value: " << MyClass::staticValue << std::endl; return 0; }
结构化绑定
解构元组类对象:std::tuple
、std::pair
、std::array
示例
-
解构元组
#include <iostream> #include <tuple> int main() { std::tuple<int, double, std::string> data = {42, 3.14, "C++17"}; // 使用结构化绑定 auto [id, value, text] = data; std::cout << "ID: " << id << ", Value: " << value << ", Text: " << text << std::endl; return 0; }
-
解构
std::pair
#include <iostream> #include <utility> int main() { std::pair<int, std::string> pairData = {1, "Hello"}; // 解构 pair auto [key, value] = pairData; std::cout << "Key: " << key << ", Value: " << value << std::endl; return 0; }
-
解构数组
#include <iostream> int main() { int arr[] = {10, 20, 30}; // 解构数组 auto [a, b, c] = arr; std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl; return 0; }
-
解构结构体
结构体必须是聚合类型,或者实现了
std::tuple_size
和std::tuple_element
,默认是按值捕获方式,可以使用auto&
、auto&&
更改捕获方式#include <iostream> #include <string> struct Person { std::string name; int age; double height; }; int main() { Person p{"Alice", 30, 5.5}; // 解构结构体 auto [name, age, height] = p; std::cout << "Name: " << name << ", Age: " << age << ", Height: " << height << std::endl; return 0; }
constexpr if
在编译时根据条件选择性地包含或排除代码,
if constexpr (condition) {
// 当 condition 为 true 时,编译器会包含这段代码
} else {
// 当 condition 为 false 时,编译器会包含这段代码
}
condition
:必须是一个编译时可以确定的布尔表达式。if constexpr
:与普通if
的区别是,编译器在编译时就会评估condition
并选择代码路径,而不是在运行时判断。
示例
#include <iostream>
template <typename T>
void printType(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral type: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating-point type: " << value << std::endl;
} else {
std::cout << "Other type" << std::endl;
}
}
int main() {
printType(42); // 输出: Integral type: 42
printType(3.14); // 输出: Floating-point type: 3.14
printType("C++17"); // 输出: Other type
return 0;
}