网站首页 > 基础教程 正文
C++20 功能特性 | 提案 |
指派初始化器 | P0329R4 |
括号形式的聚合体初始化 | P0960R3 |
禁止有用户声明构造函数的聚合体 | P1008R1 |
聚合类的类模板实参推导 | P1816R0 |
聚合体
- 数组类型
- 符合以下条件的类类型(常为 struct 或 union)
没有私有或受保护的直接 (C++17 起)非静态数据成员 | |
没有用户声明的构造函数 | (C++11 前) |
没有用户提供的构造函数(允许显式预置或弃置的构造函数) | (C++11 起) |
没有用户提供、继承或 explicit 的构造函数(允许显式预置或弃置的构造函数) | (C++17 起) |
没有用户声明或继承的构造函数 | (C++20 起) |
| |
没有虚成员函数 | |
| (C++11 起) |
聚合体初始化
T 对象 = {实参1, 实参2, ...}; | (1) | |
T 对象 {实参1, 实参2, ... }; | (2) | (C++11 起) |
T 对象 = { .指派符 = 实参1 , .指派符 { 实参2 } ... }; | (3) | (C++20 起) |
T 对象 { .指派符 = 实参1 , .指派符 { 实参2 } ... }; | (4) | (C++20 起) |
T 对象 (实参1, 实参2, ...); | (5) | (C++20 起) |
聚合类的类模板实参推导
在C++17中使用带CTAD的聚合,我们需要显式的推导指引,现在没有必要了。
template<typename T, typename U>
struct S{
T t;
U u;
};
// 在 C++17,需要推导指引
// template<typename T, typename U>
// S(T, U) -> S<T,U>;
int main(){
S s{1, 2.0}; // S<int, double>
}
如果有用户提供了推导指引,则不参与CTAD
#include <string>
template<typename T>
struct MyData{
T data;
};
MyData(const char*) -> MyData<std::string>;
int main(){
MyData s1{"abc"}; // OK, MyData<std::string> 使用推导指引
MyData<int> s2{1}; // OK, :显式模板参数
// MyData s3{1}; // Error, CTAD 不参与
}
可以推导数组类型
#include <cstdio>
template<typename T, std::size_t N>
struct Array{
T data[N];
};
int main(){
Array a{{1, 2, 3}}; // Array<int, 3>, 注意额外的括号
Array str{"hello"}; // Array<char, 6>
}
大括号省略不适用于待决名的非数组类型或待决名边界的数组类型(待决名dependent name)
#include <cstdio>
template<typename T, typename U>
struct Pair{
T first;
U second;
};
template<typename T, std::size_t N>
struct A1{
T data[N];
T oneMore;
Pair<T, T> p;
};
template<typename T>
struct A2{
T data[3];
T oneMore;
Pair<int, int> p;
};
int main(){
// A1::data 是待决名带边界的数组类型 , A1::p 是待决名类型, 大括号省略不支持
A1 a1{{1,2,3}, 4, {5, 6}}; // A1<int, 3>
// A2::data 是待决名不带边界的数组类型 , A2::p 不是是待决名类型, 大括号省略支持
A2 a2{1, 2, 3, 4, 5, 6}; // A2<int>
}
适用于包扩展。一个扩展包的尾随聚合元素对应于所有剩余元素
#include <iostream>
template<typename... Ts>
struct Overload : Ts...{
using Ts::operator()...;
};
// C++20不需要推到指引
Overload p{[](int){
std::cout << "called with int" << std::endl;
}, [](char){
std::cout << "called with char" << std::endl;
}
}; // Overload<lambda(int), lambda(char)>
int main(){
p(1); // int
p('c'); // char
}
无尾随元素包展开对应于没有元素
template<typename T, typename...Ts>
struct Pack : Ts... {
T x;
};
// 只能推导首元素
int main()
{
Pack p1{1}; // Pack<int>
Pack p2{[]{}}; // Pack<lambda()>
// Pack p3{1, []{}}; // error
}
包中的元素数量只被推导一次,但如果重复,类型应该完全匹配:
#include <tuple>
struct A{};
struct B{};
struct C{};
struct D{
operator C(){return C{};}
};
template<typename...Ts>
struct P : std::tuple<Ts...>, Ts...{
};
int main(){
P a {std::tuple<A, B, C>{}, A{}, B{}, C{}}; // P<A, B, C>
// equivalent to the above, since pack elements were deduced for
// std::tuple<A, B, C> there's no need to repeat their types
P b {std::tuple<A, B, C>{}, {}, {}, {}}; // P<A, B, C>
// since we know the whole P<A, B, C> type after std::tuple initializer, we can
// omit trailing initializers, elements will be value-initialized as usual
P c {std::tuple<A, B, C>{}, {}, {}}; // P<A, B, C>
// error, pack deduced from first initializer is <A, B, C> but got <A, B, D> for
// the trailing pack, implicit conversions are not considered
P d {std::tuple<A, B, C>{}, {}, {}, D{}};
}
https://wandbox.org/nojs/gcc-head
https://wandbox.org/nojs/clang-head
禁止使用用户声明的构造函数聚合
现在聚合类型不能有用户声明的构造函数。以前,聚合只允许有删除的或默认的构造函数。这导致了带有默认/删除构造函数的聚合的怪异行为(它们是用户声明的,而不是用户提供的)
// 以下类型在c++ 20中都不是聚合
struct S{
int x{2};
S(int) = delete; // 用户声明的构造函数
};
struct X{
int x;
X() = default; // // 用户声明的构造函数
};
struct Y{
int x;
Y(); // // 用户声明的构造函数
};
Y::Y() = default;
int main(){
S s(1); // 一直都是错误
S s2{1}; // C++17正确, C++20错误
X x{1}; // C++17正确, C++20错误
Y y{2}; // 一直都是错误
}
https://wandbox.org/permlink/cPA3m3ppHv1h8eIT
圆括号的聚会初始化
圆括号的聚合初始化现在与花括号初始化的工作方式相同,但是允许窄化转换,不允许指定的初始化器,不允许为临时对象延长生命周期,也不允许大括号省略。没有初始化式的元素是值初始化的。这允许无缝使用工厂函数,如std::make_unique<>()/emplace()。
#include <memory>
struct S{
int a;
int b = 2;
struct S2{
int d;
} c;
};
struct Ref{
const int& r;
};
int GetInt(){
return 21;
}
int main(){
//S{0.1}; // error, 花括号不允许窄化(narrowing)
S(0.1); // OK
S{.a=1}; // OK
//S(.a=1); // error, 圆括号不允许指定初始化
Ref r1{GetInt()}; // OK, 生命周期被延长
Ref r2(GetInt()); // 危险, 生命周期没被延长
S{1, 2, 3}; // OK,花括号省略, 相当于 S{1,2,{3}}
//S(1, 2, 3); // error, 不支持括号省略
// 没有初始化器的值接受默认值或值初始化(T{})
S{1}; // {1, 2, 0}
S(1); // {1, 2, 0}
// make_unique 正常工作
auto ps = std::make_unique<S>(1, 2, S::S2{3});
// 数组也支持了
int arr1[](1, 2, 3);
int arr2[2](1); // {1, 0}
}
https://wandbox.org/permlink/w8OrhnuA6WJLb4GA
指派初始化器
每个 指派符 必须指名 T 的一个直接非静态数据成员,而表达式中所用的所有 指派符 必须按照与 T 的数据成员相同的顺序出现。
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // 错误:指派符的顺序不匹配声明顺序
A b{.x = 1, .z = 2}; // OK:b.y 被初始化为 0
指派初始化器所指名的每个直接非静态数据成员,从其指派符后随的对应花括号或等号初始化器初始化。禁止窄化转换。
指派初始化器可用于将联合体初始化为其首个成员之外的状态。只可以为一个联合体提供一个初始化器。
union u { int a; const char* b; };
u f = { .b = "asdf" }; // OK:联合体的活跃成员为 b
u g = { .a = 1, .b = "asdf" }; // 错误:只可提供一个初始化器
对于非联合体的聚合体中未提供指派初始化器的元素,按上述针对初始化器子句的数量少于成员数量时的规则进行初始化(如果提供默认成员初始化器则使用它,否则为空列表初始化):
struct A {
string a;
int b = 42;
int c = -1;
};
A{.c=21} // 以 {} 初始化 a,这样会调用默认构造函数
// 然后以 = 42 初始化 b
// 然后以 = 21 初始化 c
如果以指派初始化器子句初始化的聚合体拥有一个匿名联合体成员,那么对应的指派初始化器必须指名该匿名联合体的其中一个成员。
注意:乱序的指派初始化、嵌套的指派初始化、指派初始化器与常规初始化器的混合,以及数组的指派初始化在 C 编程语言中受支持,但在 C++ 不允许。
struct A { int x, y; };
struct B { struct A a; };
struct A a = {.y = 1, .x = 2}; // 合法 C,非法 C++(乱序)
int arr[3] = {[1] = 5}; // 合法 C,非法 C++(数组)
struct B b = {.a.x = 0}; // 合法 C,非法 C++(嵌套)
struct A a = {.x = 1, 2}; // 合法 C,非法 C++(混合)
猜你喜欢
- 2024-10-10 C++系列1-1:初探C++ c=2μf
- 2024-10-10 浅谈C++11(第9篇 可变参数模板) c++可变参数模板类
- 2024-10-10 数组的初始化方式有哪几种? 数组的初始化是什么意思
- 2024-10-10 c++对于内建类型的默认初始化 创建内部类的对象
- 2024-10-10 C++类构造函数,如何初始化对象?linux C++第27讲
- 2024-10-10 【C++编程语言】之 类和对象——对象的初始和消除
- 2024-10-10 C++基础语法梳理:引用、封装、继承和多态
- 2024-10-10 C++核心准则?:按照成员声明的顺序定义和初始化成员变量
- 2024-10-10 C++核心准则C.47:按照成员变量声明的次序定义和初始化数据成员
- 2024-10-10 C/C++语言编程系列002——不同情况下数组的初始化方法
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)