[英]Why and how do the C++ implicit conversion rules distinguish templated conversion functions from non-templated?
我试图理解 C++ 中的隐式转换规则,以及为什么以下简化情况下的两个隐式转换不同:
// A templated struct.
template <typename T>
struct A {};
// A templated struct that can be constructed from anything that can be
// converted to A<T>. In reality the reason the constructor is templated
// (rather than just accepting A<T>) is because there are other constructors
// that should be preferred when both would be acceptable.
template <typename T>
struct B {
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U, A<T>>>>
B(U&& u);
};
// A struct that cna be implicitly converted to A<T> or B<T>.
struct C {
template <typename T>
operator A<T> ();
template <typename T>
operator B<T> ();
};
// Another struct that can be implicitly converted to A or B, but this time only
// a specific instantiation of those templates.
struct D {
operator A<int> ();
operator B<int> ();
};
// A function that attempts to implicitly convert from both C and D to B<int>.
void Foo() {
B<int> b_from_c = C{};
B<int> b_from_d = D{};
}
当我用 clang 编译它时,它抱怨b_from_c
初始化不明确:
foo.cc:45:10: error: conversion from 'C' to 'B<int>' is ambiguous
B<int> b_from_c = C{};
^ ~~~
foo.cc:24:3: note: candidate constructor [with U = C, $1 = void]
B(U&& u);
^
foo.cc:33:3: note: candidate function [with T = int]
operator B<T> ();
^
这对我来说完全有意义:有两条路径可以从C
转换为B<int>
。
但让我困惑的部分是为什么 clang不抱怨b_from_d
初始化。 两者之间的唯一区别是有问题的使用模板转换 function 而接受的则不使用。 我假设这与隐式转换规则或重载选择规则中的排名有关,但我不能完全把它放在一起,而且如果有的话我会期望b_from_d
被拒绝而b_from_c
被接受。
有人可以帮助我理解,最好是引用标准,为什么其中一个是模棱两可的而另一个不是?
我不是 100% 确定,但我想说这是Best viable function的第 4 点:
F1确定比F2好function...
...
4. 或者,如果不是,F1 是非模板 function 而 F2 是模板特化
在 C 的情况下, C
中的转换C
和转换构造函数B
都是模板特化并且同样可行。
在D
的情况下, D
中的转换 function 是非模板 function ,因此比转换构造函数B
更好。
引用的文本来自 C++20 标准,但链接指向 N4861。
[dcl.init.general]/17.6.3解释了这种复制初始化是如何完成的:
否则(即,对于剩余的复制初始化情况),可以从源类型转换为目标类型或(当使用转换 function 时)转换为派生的 class 的用户定义转换如 12.4.2.5 中所述进行枚举,最好的一个是通过重载决议(12.4)选择的。 如果转换无法完成或不明确,则初始化格式错误。 选择的 function 以初始化表达式作为参数调用; 如果 function 是构造函数,则调用是目标类型的 cv 非限定版本的纯右值,其结果 object 由构造函数初始化。 该调用用于根据上述规则直接初始化作为复制初始化目标的 object。
根据 12.4.2.5( N4861 中的 12.4.1.4 )
[...] 假设“ cv1
T
”是正在初始化的 object 的类型,T
是 class 类型,候选函数选择如下:
T
的转换构造函数 (11.4.8.2) 是候选函数。- 当初始化表达式的类型是 class 类型“ cv
S
”时,考虑S
及其基类的非显式转换函数。 [...] 那些没有隐藏在S
中并产生其 cv-unqualified 版本与T
类型相同或者是其派生 class 的类型的是候选函数。 对返回“对X
的引用”的转换 function 的调用是 X 类型的泛左值,因此这种转换 function 被认为为选择候选函数的过程产生X
在这两种情况下,参数列表都有一个参数,即初始化表达式。
第一个项目符号暗示构造函数B::B<D>(D&&)
是候选 function,我们将其称为F2
。 第二个项目符号暗示D::operator B<int>()
是候选 function,我们将其称为F1
。
确定F1
或F2
是否优于另一个的第一步是确定涉及的隐式转换序列( [over.match.best.general]/2([over.match.best]/2 in N4861) )。 要调用F1
,隐式转换序列是从D{}
到D::operator B<int>
(类型为D&
)的隐式 object 参数。 这是身份转换 ( [over.ics.ref]/1 )。 要调用F2
,隐式转换序列是从D{}
到构造函数的参数类型D&&
。 这也是同理的身份转换。
通常将D
右值绑定到右值引用被认为是比将其绑定到左值引用更好的隐式转换序列; [over.ics.rank]/3.2.3 。 但是,如果两个绑定中的任何一个绑定到 function 的隐式 object 参数,该参数在没有引用限定符的情况下声明(如D::operator B<int>()
),则该规则不适用。 结果是两个隐式转换序列都不比另一个好。
然后我们必须 go 通过 [over.match.best.general]/2 中的决胜规则(N4861 中的[over.match.best]/2) 。 我们可以看到项目符号 2.2 不适用于我们的案例,因为我们的上下文是 [over.match.copy],而不是 [over.match.conv] 或 [over.match.ref]。 同样,项目符号 2.3 也不适用。 这意味着 2.4 控件: F1
不是 function 模板特化,而F2
是。 重载决议成功,选择F1
。
在b_from_c
的情况下,决胜局规则均不适用,并且重载决议不明确。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.