为什么以及如何c++隐式转换规则将模板转换函数与非策略区分开?

发布于 2025-01-20 09:38:09 字数 1933 浏览 0 评论 0原文

我正在尝试理解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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

哎呦我呸! 2025-01-27 09:38:09

引用的文本来自C ++ 20标准,但链接为N4861。

[dcl.init.general] /17.6.6.6.3 说明如何进行这种副本进行启动:

否则(即,对于剩余的副本限制案例),用户定义的转换可以
从源类型转换为目标类型或(使用转换功能时)
如12.4.2.5中所述列举其派生的类别,并且通过
超负荷分辨率(12.4)。如果转换无法完成或模棱两可,则初始化为
不良。选定的函数以初始化器表达式为其参数调用;如果是
函数是构造函数,调用是目标类型的CV UNQUALIFIED版本
其结果对象由构造函数初始化。该呼叫用于直接定性化。
对于上面的规则,对象是副本定位的目的地。

根据12.4.2.5( 12.4.1.4 in N4861 in n44861

[...]假设“ cv1 t”是要初始化的对象的类型,使用t类型,候选功能
选择如下:

  • t的转换构造函数(11.4.8.2)是候选功能。
  • 当初始化器表达式的类型是类类型“ cv s”时,s的非明确转换功能考虑基础类。 [...]那些没有隐藏在s中并产生的类型的类型与t或其派生类别的类型是候选功能。调用转换函数返回“引用x”是类型X的glvalue,因此,考虑选择候选者的过程,该转换功能被认为可以产生x功能。

在这两种情况下,参数列表都有一个参数,即初始化器表达式。

第一个子弹意味着构造函数b :: b&lt; d&gt;(d&amp;&amp;)是一个候选函数,我们将调用f2。第二个子弹意味着d ::操作员b&lt; int&gt;()是候选功能,我们将调用f1

确定是否f1f2比另一个更好的第一步是确定所涉及的隐式转换序列()。要调用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的函数的隐式对象参数的情况下(AS d :: 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:

Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can
convert from the source type to the destination type or (when a conversion function is used) to a
derived class thereof are enumerated as described in 12.4.2.5, and the best one is chosen through
overload resolution (12.4). If the conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer expression as its argument; if the
function is a constructor, the call is a prvalue of the cv-unqualified version of the destination type
whose result object is initialized by the constructor. The call is used to direct-initialize, according
to the rules above, the object that is the destination of the copy-initialization.

According to 12.4.2.5 (12.4.1.4 in N4861)

[...] Assuming that "cv1 T" is the type of the object being initialized, with T a class type, the candidate functions
are selected as follows:

  • The converting constructors (11.4.8.2) of T are candidate functions.
  • When the type of the initializer expression is a class type "cv S", the non-explicit conversion functions of S and its base classes are considered. [...] Those that are not hidden within S and yield a type whose cv-unqualified version is the same type as T or is a derived class thereof are candidate functions. A call to a conversion function returning "reference to X" is a glvalue of type X, and such a conversion function is therefore considered to yield X for this process of selecting candidate functions.

In both cases, the argument list has one argument, which is the initializer expression.

The first bullet implies that the constructor B::B<D>(D&&) is a candidate function, which we'll call F2. The second bullet implies that D::operator B<int>() is a candidate function, which we'll call F1.

The first step to determine whether either F1 or F2 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 call F1, the implicit conversion sequence is from D{} to the implicit object parameter of D::operator B<int> (which is of type D&). This is an identity conversion ([over.ics.ref]/1). To call F2, the implicit conversion sequence is from D{} 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 (as D::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, and F2 is. The overload resolution succeeds, selecting F1.

In the case of b_from_c, none of the tiebreaker rules apply, and the overload resolution is ambiguous.

可爱暴击 2025-01-27 09:38:09

我不是100%确定的,但是我会说这是最佳的点4可行的功能

f1被确定为比F2 ...
更好的功能
...
4。或者,如果不是那样,F1是非模板函数,而F2是模板专业

c的模板专业化,c中的转换函数和转换构造器中的转换函数b是模板专业化,同样可行。

d的情况下,d中的转换功能是一个非模板功能,因此比转换构造函数b更好。

I'm not 100% sure, but I'd say it's point 4 from Best viable function:

F1 is determined to be a better function than F2 ...
...
4. or, if not that, F1 is a non-template function while F2 is a template specialization

In case of C, both the conversion function in C and the converting constructor B are template specializations and are equally viable.

In case of D, the conversion function in D is a non-template function and so is better than the converting constructor B.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文