CPP/语言/重载决议
在这一页面,有着以下的词条:
构造函数候选的额外规则:
在构造类型 D 的对象时,如果满足以下所有条件,那么从类类型 C 继承的首个形参类型是“到 P 的引用”的构造函数(包括从模板实例化的此类构造函数)会从候选函数集合排除:
- 实参列表只有一个实参
- C 引用关联于 P
- P 引用关联与 D
该词条比较难以理解,牵扯的概念较多,对于首次查询该词条的用户来说,难以模拟情景。但我们翻看C++标准草案,不难发现,这一部分的原文来自:
[n4849]
12.4.1 Candidate functions and argument lists [over.match.funcs]
…
8 … A constructor inherited from class type C (11.10.3) that has a first parameter of type “reference to cv1 P” (including such a constructor instantiated from a template) is excluded from the set of candidate functions when constructing an object of type cv2 D if the argument list has exactly one argument and C is reference-related to P and P is reference-related to D.
同时,标准草案给出了示例:
struct A {
A(); // #1
A(A &&); // #2
template<typename T> A(T &&); // #3
};
struct B : A {
using A::A;
B(const B &); // #4
B(B &&) = default; // #5, implicitly deleted
struct X { X(X &&) = delete; } x;
};
extern B b1;
B b2 = static_cast<B&&>(b1); // calls #4: #1 is not viable, #2, #3, and #5 are not candidates
struct C { operator B&&(); };
B b3 = C(); // calls #4
直观地解释了上述词条。当然,草案中的示例代码涵盖的情况较多,我们再简化代码,方便理解,举一反三即可:
struct A {
A() { }
A(const A&) {
std::cout << "A" << std::endl;
}
};
struct B : A {
using A::A;
};
B b { B{} };
结果这个程序不会输出"A"。
现在我们来逐步分析:
- 类型B(对应类型D)继承类型A(对应类型C),所以类型A引用关联于B
- 类型B从类型A继承了两个构造函数:
A::A()
和A::A(const A&)
- 并且构造函数
A::A(const A&)
的首个形参类型为到A
(对应类型P)的引用,而A
类型显然和自身类型(对应类型C)相似,自身类型引用关联于类型A(对应类型C引用关联于类型P) - 在构造对象
b
时,实参列表只有B{}
这一个实参,到这里,符合了词条中描述的三个条件 - 因此,在选择构造函数时,
A::A(const A&)
不会作为候选函数,而会选择类型B的隐式声明的移动构造函数。
注释[1]:引用关联于
类型T1
,T2
的无cv限定类型U1
,U2
,如果U1
于U2
相似,或者U1
是U2
的基类,则T1
引用关联于T2
CPP/语言/隐式转换
在该页面以及本文中,出现了一个概念:“相似类型”
它在cppreference上的描述如下:
非正式地说,忽略顶层 cv 限定性,如果两个类型符合下列条件,那么它们相似:
- 它们是同一类型;或
- 它们都是指针,且被指向的类型相似;或
- 它们都是指向相同类的成员指针,且被指向的成员类型相似;或
- 它们都是数组,且数组元素类型相似。
正式地说,类型的相似性基于它们的限定性分解进行定义。
类型 T 的限定性分解 是包含组分 cv_i 和 P_i 的序列,它们对于某些非负 n 可以将 T 分解为 “cv_0 P_0 cv_1 P_1 … cv_n−1 P_n−1 cv_n U”,其中:每个 cv_i 都是一个可以包含
const
和volatile
的集合。每个 P_i 都是以下之一:
- “指向【某类型】的指针”。
- “指向类 C_i 的【某类型】成员的指针”。
- “包含 N_i 个【某类型】元素的数组”。
- “包含【某类型】元素且边界未知的数组”。
对于类型 T1 和 T2,如果它们各自有一个限定性分解,使得这两个限定性分解满足以下所有条件,那么这两个类型相似:
- 它们具有相同的 n。
- 它们的 U 指代的类型相同。
- 所有的 i 对应的每对 P_i 组分都各自相同
对于相似类型的非正式解释是比较简单易懂的(或者说,就是对正式解释的总结),这里不再赘述。然而,其正式解释却让人摸不着头脑,难以读懂,这里我们结合cppreference上的示例进行分析。
using T1 = const int* volatile *
;
using T2 = int** const;
T1与T2相似。
就上述案例而言,我们用限定性分解的方式来进行解释。
在此之前,我们先了解限定性分解(cv-decomposition)中的三个要素:n
, cv_i
,P_i
和U
- 限定性分解中的
n
,代表了你要对这个类型打算分解几层 - 而
cv_i
,代表了所在的这一层的类型的cv限定类型 P_i
,代表了所在层的指针类型(U
则代表最后一层的类型)
所谓限定性分解,就是将多级指针(从外向内)逐层分解(n次),得到每一层指针的类型和其cv限定符。
因此,上述案例中,我们进行限定性分解得到:
对T1而言:
- n=0时,U是一个指针,cv_0=空,指向一个volatile指针,这个volatile指针指向
const int
- n=1时,P_0是和上文U类型相同的指针,cv_0=空
- U是一个指针,cv_1=volatile,指向
const int
- U是一个指针,cv_1=volatile,指向
- n=2时,P_0是和上文U类型相同的指针,cv_0=空
- P_1是一个指针,cv_1=volatile,指向
const int
- U是
int
,cv_2=const
- P_1是一个指针,cv_1=volatile,指向
对T2而言:
- n=0时,U是一个指针,cv_0=const,指向一个指针,这个指针指向
int
- n=1时,P_0是一个指针,cv_0=const
- U是一个指针,cv_1=空,指向
int
- U是一个指针,cv_1=空,指向
- n=2时,P_0是一个指针,cv_0=const
- P_1是一个指针,cv_1=空,指向
int
- U是
int
,cv_2=空
- P_1是一个指针,cv_1=空,指向
对于T1和T2,它们各自拥有三个限定性分解,对于前两个限定性分解而言,其类型U不相同,但对于n=2时的限定性分解而言:
- 其U类型都是
int
- n相同
- 每一对P_0、P_1都是指向某类型的指针(或者说,组分相同)
因此,T1和T2满足相似的条件,两类型相似。
总结:遇到相似性难以判定的情况时,如果要采用限定性分解判定相似类型,应逐层剖析指针,提取cv限定,比较最长限定分解的情况即可。(两种类型的最长限定分解的n不等时,其余所有n相等时的限定性分解的U均不相同,可自行推导)