【问题标题】:"Ambiguous conversion sequence" - what is the purpose of this concept?“模棱两可的转换序列”——这个概念的目的是什么?
【发布时间】:2019-07-23 17:58:54
【问题描述】:

在 N4659 16.3.3.1 隐式转换序列

10 如果存在多个不同的转换序列,每个转换序列都将参数转换为参数类型,则与参数关联的隐式转换序列被定义为指定为不明确的唯一转换序列转换顺序。出于对 16.3.3.2 中所述的隐式转换序列进行排序的目的,不明确的转换序列被视为用户定义的转换序列,与任何其他用户定义的转换序列没有区别[注意:此规则防止函数变为非-viable,因为它的参数之一的转换序列不明确。] 如果使用不明确的转换序列的函数被选为最佳可行函数,则调用将是错误的,因为调用中的参数之一的转换是模棱两可的。

(当前草稿对应部分为12.3.3.1

这个规则的预期目的和它引入的模糊转换序列的概念是什么?

文本中提供的注释指出,此规则的目的是“防止函数因其中一个参数的转换顺序不明确而变得不可行”。嗯...这实际上指的是什么? 可行函数 的概念在文档的preceding 部分中定义。它根本不依赖于转换的歧义(每个参数的转换必须存在,但它们不必是明确的)。而且似乎没有规定 viable 函数以后会以某种方式“变得不可行”(既不是因为一些歧义,也不是因为其他任何事情)。列举了可行的函数,它们根据certain rules相互竞争成为“最好的”,如果有一个“赢家”,则解析成功。在这个过程中,一个可行的功能可能(或需要)变成一个不可行的功能。

上述段落中提供的示例不是很有启发性(即不清楚上述规则在该示例中扮演什么角色)。


这个问题最初与这个简单的例子有关

struct S
{
  operator int() const { return 0; };
  operator long() const { return 0; };
};

void foo(int) {}

int main()
{
  S s;
  foo(s);
}

让我们在这里机械地应用上述规则。 foo 是一个可行的功能。从参数类型S 到参数类型int 有两个隐式转换序列:S -> intS -> long -> int。这意味着根据上述规则,我们必须将它们“打包”成一个模糊的转换序列。然后我们得出结论foo最佳可行函数。然后我们发现它使用了我们的模糊转换序列。因此,根据上述规则,代码格式错误。

这似乎没有意义。这里的自然期望是应该选择S -> int 转换,因为它的排名高于S -> long -> int 转换。我知道的所有编译器都遵循“自然”的重载决议。

那么,我误会了什么?

【问题讨论】:

  • 荒谬的转换序列旨在排除恒等转换序列。 Like this
  • @Johannes Schaub - litb:引用的文字并没有说只有同等排名的转换序列必须“打包”到一个不明确的转换序列中。它只是说“如果存在多个不同的转换序列......”如果您有多个不同的序列,则必须将它们打包成一个模糊的转换序列,无论其中一个是否更好是否比其他人。
  • @Johannes Schaub - litb:我不明白你在说什么。再次:我在这里看到两个不同的转换序列S -> intS -> long -> int。怎么只有一个?请注意,这种将多个转换序列“打包”到一个模糊转换序列的过程很早就完成了:在我们开始对隐式转换序列进行排名并选择最佳可行函数之前。
  • @AnT 对应的规则建立-> int-> long 部分表示“重载分辨率用于选择要调用的转换函数”。 (请注意,这是一个 嵌套 重载决议,具有自己的隐式转换序列,不要与 foo 的重载决议混淆)。在这种情况下,过载解决方案使用仲裁 (eel.is/c++draft/over.match#best-2.2) 选择了operator int,因为int -> int 优于long -> int。所以这里没有用户定义的使用operator long的转换序列。

标签: c++ language-lawyer implicit-conversion overload-resolution conversion-operator


【解决方案1】:

为了可行,必须有一个implicit conversion sequence

该标准本可以允许多个隐式转换序列,但这可能会使确定选择哪个重载的措辞更加复杂。

因此标准最终为每个参数的每个参数定义了一个且恰好一个隐式转换序列。在有歧义的情况下,它使用的是歧义转换序列

一旦完成,它就不再需要处理一个参数到一个参数的多个转换序列的可能性。

想象一下,如果我们用 C++ 编写它。我们可能有几种类型:

namespace conversion_sequences {
  struct standard;
  struct user_defined;
  struct ellipsis;
}

其中每个都有很多东西(此处跳过)。

由于每个转换序列都是上述之一,我们定义:

  using any_kind = std::variant< standard, user_defined, ellipsis >;

现在,我们遇到了给定参数和参数有多个转换序列的情况。此时我们有两个选择。

我们可以为给定的参数、参数对传递using any_kinds = std::vector&lt;any_kind&gt;,并确保处理选择转换序列的所有逻辑都处理这个向量...

或者我们可以注意到,一个或多个入口向量的处理从不查看向量中的元素,它被视为一种user_defined 转换序列,直到我们生成的最后一个错误。

存储额外的状态并拥有额外的逻辑是一种痛苦。我们知道我们不需要那个状态,也不需要代码来处理向量。所以我们只定义了conversion_sequence::user_defined 的子类型,在找到首选重载后的代码会检查所选择的重载是否会产生错误。

虽然 C++ 标准并非(总是)在 C++ 中实现,而且其措辞与其实现之间不一定存在 1:1 的关系,但编写一个健壮的标准文档是一种编码,并且存在一些相同的问题申请。

【讨论】:

  • 这在阅读较旧的草稿时更有意义,其中上一段说“如果找不到将参数转换为参数类型的转换序列,或者转换格式不正确,则无法形成隐式转换序列”。对于“否则格式错误”,我认为他们在尝试在 UCS 中查找用户定义的转换时会出现歧义。在当前的草案中,“否则格式错误”的部分已经消失。
猜你喜欢
  • 2018-05-28
  • 2018-06-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
相关资源
最近更新 更多