简体   繁体   English

C++ 隐式转换规则为什么以及如何区分模板转换函数和非模板转换函数?

[英]Why and how do the C++ implicit conversion rules distinguish templated conversion functions from non-templated?

I'm trying to understand the implicit conversion rules in C++, and why the two implicit conversions in the following reduced case differ:我试图理解 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{};
}

When I compile this with clang, it complains about the b_from_c initialization being ambiguous:当我用 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> ();
  ^

This totally makes sense to me: there are two paths to convert from C to B<int> .这对我来说完全有意义:有两条路径可以从C转换为B<int>

But the part that puzzles me is why clang doesn't complain about the b_from_d initialization.但让我困惑的部分是为什么 clang抱怨b_from_d初始化。 The only difference between the two is that the problematic one uses a templated conversion function and the accepted one doesn't.两者之间的唯一区别是有问题的使用模板转换 function 而接受的则不使用。 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.我假设这与隐式转换规则或重载选择规则中的排名有关,但我不能完全把它放在一起,而且如果有的话我会期望b_from_d被拒绝而b_from_c被接受。

Can someone help me understand, ideally with citations of the standard, why one of these is ambiguous and the other isn't?有人可以帮助我理解,最好是引用标准,为什么其中一个是模棱两可的而另一个不是?

I'm not 100% sure, but I'd say it's point 4 from Best viable function :我不是 100% 确定,但我想说这是Best viable function的第 4 点:

F1 is determined to be a better function than F2... F1确定比F2好function...
... ...
4. or, if not that, F1 is a non-template function while F2 is a template specialization 4. 或者,如果不是,F1 是非模板 function 而 F2 是模板特化

In case of C , both the conversion function in C and the converting constructor B are template specializations and are equally viable.在 C 的情况下, C中的转换C和转换构造函数B都是模板特化并且同样可行。

In case of D , the conversion function in D is a non-template function and so is better than the converting constructor B .D的情况下, D中的转换 function 是非模板 function ,因此比转换构造函数B更好。

Quoted text is from the C++20 standard, but links are to N4861.引用的文本来自 C++20 标准,但链接指向 N4861。

[dcl.init.general]/17.6.3 explains how such a copy-initialization is done: [dcl.init.general]/17.6.3解释了这种复制初始化是如何完成的:

Otherwise (ie, 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).否则(即,对于剩余的复制初始化情况),可以从源类型转换为目标类型或(当使用转换 function 时)转换为派生的 class 的用户定义转换如 12.4.2.5 中所述进行枚举,最好的一个是通过重载决议(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;选择的 function 以初始化表达式作为参数调用; 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.如果 function 是构造函数,则调用是目标类型的 cv 非限定版本的纯右值,其结果 object 由构造函数初始化。 The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.该调用用于根据上述规则直接初始化作为复制初始化目标的 object。

According to 12.4.2.5 ( 12.4.1.4 in N4861 )根据 12.4.2.5( N4861 中的 12.4.1.4

[...] Assuming that " cv1 T " is the type of the object being initialized, with T a class type, the candidate functions are selected as follows: [...] 假设“ cv1 T ”是正在初始化的 object 的类型, T是 class 类型,候选函数选择如下:

  • The converting constructors (11.4.8.2) of T are candidate functions. T的转换构造函数 (11.4.8.2) 是候选函数。
  • 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.当初始化表达式的类型是 class 类型“ cv S ”时,考虑S及其基类的非显式转换函数。 [...] 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. [...] 那些没有隐藏在S中并产生其 cv-unqualified 版本与T类型相同或者是其派生 class 的类型的是候选函数。 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.对返回“对X的引用”的转换 function 的调用是 X 类型的泛左值,因此这种转换 function 被认为为选择候选函数的过程产生X

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 .第一个项目符号暗示构造函数B::B<D>(D&&)是候选 function,我们将其称为F2 The second bullet implies that D::operator B<int>() is a candidate function, which we'll call F1 .第二个项目符号暗示D::operator B<int>()是候选 function,我们将其称为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) ).确定F1F2是否优于另一个的第一步是确定涉及的隐式转换序列( [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& ).要调用F1 ,隐式转换序列是从D{}D::operator B<int> (类型为D& )的隐式 object 参数。 This is an identity conversion ( [over.ics.ref]/1 ).这是身份转换 ( [over.ics.ref]/1 )。 To call F2 , the implicit conversion sequence is from D{} to the constructor's parameter type, D&& .要调用F2 ,隐式转换序列是从D{}到构造函数的参数类型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;通常将D右值绑定到右值引用被认为是比将其绑定到左值引用更好的隐式转换序列; [over.ics.rank]/3.2.3 . [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).但是,如果两个绑定中的任何一个绑定到 function 的隐式 object 参数,该参数在没有引用限定符的情况下声明(如D::operator B<int>() ),则该规则不适用。 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) .然后我们必须 go 通过 [over.match.best.general]/2 中的决胜规则(N4861 中的[over.match.best]/2) 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].我们可以看到项目符号 2.2 不适用于我们的案例,因为我们的上下文是 [over.match.copy],而不是 [over.match.conv] 或 [over.match.ref]。 Similarly bullet 2.3 does not apply.同样,项目符号 2.3 也不适用。 This means 2.4 controls: F1 is not a function template specialization, and F2 is.这意味着 2.4 控件: F1不是 function 模板特化,而F2是。 The overload resolution succeeds, selecting F1 .重载决议成功,选择F1

In the case of b_from_c , none of the tiebreaker rules apply, and the overload resolution is ambiguous.b_from_c的情况下,决胜局规则均不适用,并且重载决议不明确。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM