为什么以及如何c++隐式转换规则将模板转换函数与非策略区分开?
我正在尝试理解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&lt; int
。
但是让我感到困惑的部分是为什么Clang 不抱怨b_from_d
初始化。两者之间的唯一区别是,有问题的一个人使用模板转换函数,而被接受的转换功能则没有。我认为这与在隐式转换规则或过载选择规则中排名有关,但我不能完全将其放在一起,如果我期望有任何事情b_from_d
将被拒绝和<代码> B_FROM_C 被接受。
有人可以帮助我理解,理想情况下,使用标准的引用,为什么其中一个是模棱两可的,另一个不是?
I'm trying to understand the implicit conversion rules in C++, and why the two implicit conversions in the following reduced case differ:
// 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{};
}
When I compile this with clang, it complains about the b_from_c
initialization being ambiguous:
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> ();
^
This totally makes sense to me: there are two paths to convert from C
to B<int>
.
But the part that puzzles me is why clang doesn't complain about the b_from_d
initialization. The only difference between the two is that the problematic one uses a templated conversion function and the accepted one doesn't. I assume this has something to do with ranking in the implicit conversion rules or the overload selection rules, but I can't quite put it together and also if anything I would have expected b_from_d
to be rejected and b_from_c
to be accepted.
Can someone help me understand, ideally with citations of the standard, why one of these is ambiguous and the other isn't?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
引用的文本来自C ++ 20标准,但链接为N4861。
[dcl.init.general] /17.6.6.6.3 说明如何进行这种副本进行启动:
根据12.4.2.5( 12.4.1.4 in N4861 in n44861 )
第一个子弹意味着构造函数
b :: b&lt; d&gt;(d&amp;&amp;)
是一个候选函数,我们将调用f2
。第二个子弹意味着d ::操作员b&lt; int&gt;()
是候选功能,我们将调用f1
。确定是否
f1
或f2
比另一个更好的第一步是确定所涉及的隐式转换序列()。要调用f1
,隐式转换序列是从d {}
到d :: ocerator b&lt; int&gt;
的隐式对象参数(哪个(哪个)是类型d&amp;
)。这是一个身份转换( 1 )。要调用f2
,隐式转换序列是从d {}
到构造函数的参数类型,d&amp;&amp;&amp;
。出于相同的原因,这也是一个身份转换。通常,将
d
rvalue绑定到rvalue参考将被认为是更好的隐式转换序列,而不是将其绑定到lvalue参考。 [over.ics.rank] /3.2.3 << /a>。但是,在两个绑定中的任何一个中的任何一个都不适用于没有ref-qualifier的函数的隐式对象参数的情况下(ASd :: operator b&lt; int&gt;()
,该规则是不适用的。曾是)。结果是,两个隐式转换序列都不比另一个更好。然后,我们必须在上.best.general]/2(N4861中的[over.match.best]/2)。我们可以看到子弹2.2不适用于我们的案例,因为我们的上下文是[over.match.copy],而不是[over.match.conv]或[over.match.ref]。类似地,子弹2.3也不适用。这意味着2.4控件:
f1
不是函数模板专业化,f2
是。过载分辨率成功,选择f1
。在
b_from_c
的情况下,未应用决胜局规则,并且超载分辨率模棱两可。Quoted text is from the C++20 standard, but links are to N4861.
[dcl.init.general]/17.6.3 explains how such a copy-initialization is done:
According to 12.4.2.5 (12.4.1.4 in N4861)
The first bullet implies that the constructor
B::B<D>(D&&)
is a candidate function, which we'll callF2
. The second bullet implies thatD::operator B<int>()
is a candidate function, which we'll callF1
.The first step to determine whether either
F1
orF2
is better than the other is to determine the implicit conversion sequences involved ([over.match.best.general]/2 ([over.match.best]/2 in N4861)). To callF1
, the implicit conversion sequence is fromD{}
to the implicit object parameter ofD::operator B<int>
(which is of typeD&
). This is an identity conversion ([over.ics.ref]/1). To callF2
, the implicit conversion sequence is fromD{}
to the constructor's parameter type,D&&
. This is also an identity conversion for the same reason.Normally binding a
D
rvalue to an rvalue reference would be considered a better implicit conversion sequence than binding it to an lvalue reference; [over.ics.rank]/3.2.3. However, that rule is inapplicable in cases where either of the two bindings is to the implicit object parameter of a function that was declared without a ref-qualifier (asD::operator B<int>()
was). The result is that neither of the two implicit conversion sequences is better than the other.We then have to go through the tiebreaker rules in [over.match.best.general]/2 ([over.match.best]/2 in N4861). We can see that bullet 2.2 does not apply to our case since our context is [over.match.copy], and not [over.match.conv] nor [over.match.ref]. Similarly bullet 2.3 does not apply. This means 2.4 controls:
F1
is not a function template specialization, andF2
is. The overload resolution succeeds, selectingF1
.In the case of
b_from_c
, none of the tiebreaker rules apply, and the overload resolution is ambiguous.我不是100%确定的,但是我会说这是最佳的点4可行的功能:
c
的模板专业化,c
中的转换函数和转换构造器中的转换函数b
是模板专业化,同样可行。在
d
的情况下,d
中的转换功能是一个非模板功能,因此比转换构造函数b
更好。I'm not 100% sure, but I'd say it's point 4 from Best viable function:
In case of
C
, both the conversion function inC
and the converting constructorB
are template specializations and are equally viable.In case of
D
, the conversion function inD
is a non-template function and so is better than the converting constructorB
.