【问题标题】:what are the overload resolution rules of list-initialization列表初始化的重载解析规则是什么
【发布时间】:2020-03-14 18:04:05
【问题描述】:

这里有一些代码

#include <iostream>
struct A {
    A(int) {}
};
struct B {
    B(A) {
        std::cout<<"0"<<std::endl;
    }
    B(B const&) {
        std::cout << "1" << std::endl;
    }
    B(B&&) {
        std::cout << "2" << std::endl;
    }
};
int main() {
    B b0{{0}}; // this is ok  #1
    B b( {0} ); //this is error #2
}

g++ 报告:

main.cpp: In function ‘int main()’:
main.cpp:17:11: error: call of overloaded ‘B(<brace-enclosed initializer list>)’ is ambiguous
  B b({ 0 });
           ^
main.cpp:12:2: note: candidate: B::B(B&&)
  B(B&&) {
  ^
main.cpp:9:2: note: candidate: B::B(const B&)
  B(B const&) {
  ^
main.cpp:6:2: note: candidate: B::B(A)
  B(A) {

叮当报告:

main.cpp:17:4: error: call to constructor of 'B' is ambiguous
        B b({ 0 });
          ^ ~~~~~
main.cpp:6:2: note: candidate constructor
        B(A) {
        ^
main.cpp:12:2: note: candidate constructor
        B(B&&) {
        ^
main.cpp:9:2: note: candidate constructor
        B(B const&) {
        ^
1 error generated.

{0} 将转换为临时对象 A 并选择构造器 B(A), #1 和#2 都是“直接构造函数”的形式,为什么#1 可以,#2 有三个候选构造函数并且是模棱两可的?

【问题讨论】:

  • {0} 也可以是 B,从 0 隐式转换为 A
  • @Jarod42 这是问题,#1 使用 B(A) 而#2 是模棱两可的?
  • 一个{0};乙 b { 0 };这些都是有效的。那么,在 B b( { 0 }) 中,{ 0 } 代表什么? A 或 B 的实例?没有人知道,这就是它模棱两可的原因:您是在调用 B ( A ) 还是在调用 B ( const B& ) ?甚至是 B ( B && ) ?
  • 而 #1 有效,因为 B b0{ 不管 };调用构造函数,而不是复制或移动构造函数。
  • @MFnx2 谢谢,你有一些关于你的解释“B b0{whatever};调用构造函数,而不是复制或移动构造函数”的c ++标准引用吗?

标签: c++ c++11 language-lawyer


【解决方案1】:

因为对于#1,[over.best.ics]/4(强调我的)不允许复制和移动构造函数:

但是,如果目标是

  • 构造函数的第一个参数
  • 用户定义的转换函数的隐式对象参数

并且构造函数或用户定义的转换函数是候选函数 由

  • [over.match.ctor],当参数是类复制初始化第二步中的临时参数时,

  • [over.match.copy]、[over.match.conv] 或 [over.match.ref](在所有情况下),或

  • [over.match.list] 的第二阶段,当初始化列表恰好有一个元素本身就是初始化列表,并且目标 是类 X 的构造函数的第一个参数,转换 是对 X 或对 cv X 的引用,

不考虑用户定义的转换序列。 [ 注意:这些 规则阻止应用多个用户定义的转换 在重载决议期间,从而避免无限递归。 — 尾注 ]

所以区分({...}){{...}}的是语言规则。请注意,({...}) 案例属于 [over.match.ctor] 但参数不是类复制初始化第二步中的临时参数,因此第一个项目符号不适用。

您可以进一步阅读 Issue 2076 以了解它旨在禁止 {{...}} 案例中内括号的复制和移动构造函数:

问题 1467 的决议提出了一些合理的构想 格式不正确。例如,

struct A { A(int); };
struct B { B(A); };
B b{{0}};

这现在是模棱两可的,因为文本不允许用户定义 B 的复制和移动构造函数的转换已从 16.3.3.1 [over.best.ics] 第 4 段...

【讨论】:

  • for B{{anything}}, "{anything}" 满足“当初始化列表恰好有一个元素本身就是初始化列表时”这个条件和“{anything}”被转换为类A的临时对象,命名为tmp,既然“tmp”满足“当参数是类复制初始化的第二步中的临时”这个条件,那么这些“用户定义的转换序列”是不允许的吗?对于此上下文中的“B({anything})”,A 类的临时对象不是由“当初始化列表恰好有一个本身就是初始化列表的元素”规则生成的,所以这些是允许的吗?
  • @jackX 注意“... [over.match.list]的第二阶段...”。
  • “参数是类复制初始化第二步中的临时参数”是什么意思
  • @jackX 与[dcl.init]/17.6.3有关。 P0135删除了关于“临时对象”的措辞。我猜这是标准的缺陷。
  • 谢谢,临时对象是由覆盖构造函数或函数从表达式转换而来的目标对象?另一个问题是'当参数是类复制初始化的第二步中的临时参数'----这种情况会阻止什么情况,你能举个例子来帮助我理解这种情况吗?
【解决方案2】:

为什么是 B b( {0} );不行吗?

A a { 0 };
B b { 0 };

这些都是有效的。那么,在B b( { 0 }) 中,{ 0 } 代表什么?它可以是AB 的实例。没有人知道,这就是为什么它是模棱两可的。 正如您的编译器所述:有 3 个候选者:

B::B(A)
B::B(const B&)
B::B(B&&)

为什么 B b0 { {0} };工作吗?

因为您只需调用构造函数B::B(A)。复制/移动构造函数在这里被调用,只有构造函数被调用。仅当您使用另一个实例的 lvalue 初始化实例时才会调用复制构造函数:

B b0 {};
B b1 { b0 }; // copy constructor

同样适用于移动构造函数:

B b0 {};
B b1 { std::move(b0) }; // move constructor

现在,如果您将实例作为prvalue 传递,它将调用构造函数,因为该实例可以即时构造,即:prvalue 不需要复制或移动,它可以直接构造成b0。示例:

B b0 { {0} };

这里,{0} 可以解释为 prvalue 类型为 BA,但在这两种情况下,构造函数都被调用

【讨论】:

  • 在我的理解中,"B b{whatever}" 是任意构造函数,这种形式命名为直接列表初始化,简而言之,候选函数应该是B类的所有构造函数并使用重载决议来决定哪个是最佳匹配,我感到困惑的是“B b(whatever)”和“B b{whatever}”都是直接构造函数,为什么它们之间的重载决议结果不同?
  • 这并不能真正解释什么。如果{0} 在第一种情况下可以是 A 或 B 的一个实例,为什么在第二种情况下它也不能是?
  • @MM 完全同意。无论“B {anything}”还是“B(anything)”,它们都是直接构造函数
  • @MM 因为在 #1 的上下文中,只有 B::B(A) 是候选者,因此 {0} 属于 A 类型。我将编辑答案以明确这一点.
  • @MFnx 为什么“T t7 { T{} }”没有调用复制/移动构造函数?因为编译器针对省略构造函数规则进行了优化
猜你喜欢
  • 1970-01-01
  • 2017-04-07
  • 2011-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-16
相关资源
最近更新 更多