“模棱两可的转换序列”-这个概念的目的是什么?

6
在N4659的16.3.3.1隐式转换序列中写道:10如果存在几个将参数转换为参数类型的不同转换序列,则与参数关联的隐式转换序列被定义为唯一指定的模棱两可的转换序列。 为了按照16.3.3.2所述的顺序对隐式转换序列进行排名,认为模棱两可的转换序列是一个无法区分于任何其他用户定义的转换序列的用户定义的转换序列[注:此规则防止函数因其参数的模糊转换序列而变得不可行]。 如果选择使用具有模糊转换序列的函数作为最佳可行函数,则调用将是不正确的,因为通话中的一个参数的转换是模棱两可的。

(当前草案的相应部分是12.3.3.1

这条规则和它引入的模棱两可的转换序列概念的预期目的是什么?

文本中提供的注释说明了这条规则的目的是“防止由于一个参数的模糊转换序列导致函数成为不可行的”。嗯......这实际上是什么意思呢?可行函数的概念在文件的前面的部分中定义。它根本不依赖于转换的模糊性(每个参数的转换必须存在,但它们不必是明确的)。似乎没有规定一个可行的函数可以以某种方式后来“变得不可行”(既不因为有些模糊也不因为其他任何原因)。可行的函数被枚举,它们相互竞争以遵循某些规则并且如果有单个“赢家”,则解决方案将成功。在此过程中,一个可行的函数可能(或需要)不能成为一个不可行的函数。

所提供的示例并没有提供太多启示(即不清楚以上规则在该示例中扮演了什么角色)。


此简单示例引发问题。
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转换排名更高。我所知道的所有编译器都遵循这种“自然”的重载解析。
那么,我理解错了什么?

2
疯狂猜测模棱两可的转换序列旨在排除恒等转换序列。像这样 - AndyG
2
@Johannes Schaub - litb:引用的文本并没有说只有相等级别的转换序列才需要“打包”成一个模棱两可的转换序列。它只是说:“如果存在几个不同的转换序列...”如果你有几个不同的序列,它们必须被打包成一个模棱两可的转换序列,无论其中是否有一个比其他序列更好。 - AnT stands with Russia
2
@ Johannes Schaub - litb:我不明白你在说什么。再说一遍:我看到这里有两个不同的转换序列“S -> int”和“S -> long -> int”。怎么可能只有一个?请注意,将多个转换序列打包成一个模糊的转换序列是相当早的事情:在我们开始排列隐式转换序列并选择最佳可行函数之前就完成了。 - AnT stands with Russia
3
针对 -> int-> long 部分建立相应规则的说明是:“使用重载决议来选择要调用的转换函数”。(注意,这是一个嵌套的重载决议,具有自己的隐式转换序列,不应与 foo 的决议混淆。)在这种情况下,重载决议使用一种平局解决方法进行选择,即选取了 operator int,因为 int -> intlong -> int 更好。因此,在这里没有使用 operator long 的用户定义转换序列。 - Johannes Schaub - litb
3
我知道这很令人困惑。 - cpplearner
显示剩余8条评论
1个回答

3
为了可行,必须存在一个隐式转换序列
标准可以允许多个隐式转换序列,但这可能会使确定选择哪个重载的措辞更加复杂。
因此,标准最终定义了每个参数到每个参数的一个且仅有一个隐式转换序列。在存在歧义的情况下,它使用的是模棱两可的转换序列
完成后,它就不再需要处理一个参数到一个参数的多个转换序列的可能性。
想象一下,如果我们在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<any_kind>,并确保处理选取转换序列的所有逻辑都处理这个向量...
另一种是注意到处理1个或多个条目向量时从不查看向量中的元素,并且它被视为一种user_defined转换序列,直到最后生成错误。
存储额外状态并具有额外的逻辑很麻烦。我们知道我们不需要那个状态,也不需要处理向量的代码。因此,我们只需定义conversion_sequence::user_defined的子类型,然后在找到首选重载后的代码检查所选重载是否应生成错误。
虽然C++标准不总是用C++实现,措辞也不必与其实现有1:1的关系,但编写健壮的标准文档是一种编码,一些相同的问题也适用。

3
在阅读旧版草案时,这个说法更加有意义,因为前面的段落中提到:“如果没有找到将参数转换为参数类型的转换序列,或者转换无效,就无法形成隐式转换序列。” 通过“否则无效”,我认为他们考虑到了在尝试在用户自定义转换序列中查找时出现的歧义。在当前的草案中,“否则无效”的部分已经被删除。 - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接