使用列表初始化调用Ambigous构造函数

时间:2017-04-28 05:11:55

标签: c++ c++11 list-initialization

finalFile = new File(fileUri.getPath());
SendFileToserver(finalFile)

struct A { A(int) {} }; struct B { B(A) {} }; int main() { B b({0}); } 的构造会出现以下错误:

b

我期待被调用In function 'int main()': 24:9: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous 24:9: note: candidates are: 11:2: note: B::B(A) 10:8: note: constexpr B::B(const B&) 10:8: note: constexpr B::B(B&&) ,为什么在这种情况下它是不明确的?

3 个答案:

答案 0 :(得分:4)

给定一个类A,其中包含用户定义的构造函数:

struct A
{
    A(int) {}
};

和另一个B,接受A作为构造函数参数:

struct B
{
    B(A) {}
};

然后为了执行如下的初始化:

B b({0});

编译器必须考虑以下候选者:

B(A);         // #1
B(const B&);  // #2
B(B&&);       // #3

尝试查找从{0}到每个参数的隐式转换序列。

请注意B b({0})没有list-initialize b - (copy-)list-initialization适用于构造函数参数本身。

由于参数是初始化列表,因此将参数与参数匹配所需的隐式转换序列是根据列表初始化序列[over.ics.list]/p1定义的:

  

当参数是初始化列表([dcl.init.list])时,它不是表达式,并且特殊规则适用于将其转换为参数类型。

它的内容如下:

  

[...],如果参数是非聚合类X,则每13.3.1.7的重载决策选择一个   X的最佳构造函数,用于从参数初始化列表中执行X类型对象的初始化,   隐式转换序列是用户定义的转换序列,具有第二个标准转换   序列身份转换。如果多个构造函数是可行的,但没有一个比其他构造函数更好,那么   隐式转换序列是不明确的转换序列。允许用户定义的转换   用于将初始化列表元素转换为构造函数参数类型,除非在13.3.3.1中注明。

要使#1可行,以下呼叫必须有效:

A a = {0};
由于[over.match.list]/p1

是正确的

  

- 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成。 / p>

即,类A有一个接受int参数的构造函数。

要使#2成为有效候选人,以下呼叫必须有效:

const B& b = {0};

根据[over.ics.ref]/p2

  

当引用类型的参数没有直接绑定到参数表达式时,转换序列是根据[over.best.ics]将参数表达式转换为引用类型所需的转换序列。从概念上讲,此转换序列对应于使用参数表达式复制初始化引用类型的临时值。顶级cv资格的任何差异都归入初始化本身,并不构成转换。

转换为:

B b = {0};

再次关注[over.ics.list]/p6

  

允许用户定义的转换将初始化列表元素转换为构造函数参数类型[...]

允许编译器使用用户定义的转换:

A(int);

将参数0转换为B的构造函数参数A

对于候选人#3,同样的推理适用于#2。最终,编译器无法在上述隐式转换序列 {citation needed} 之间进行选择,并报告歧义。

答案 1 :(得分:3)

B b({0})可能会导致对以下任一情况的调用:

  1. B::B(A)

  2. 复制B的构造函数:从B构建临时{0}对象, 然后将其复制到b

  3. 因此含糊不清。

    如果你调用B b{0},它可以解决,它直接使用定义的构造函数而不涉及复制构造函数。

    修改

    关于第2点的有效性:

    B有一个接受A的构造函数。现在,A可以由int构建。此外,int可以通过初始化列表构建。这就是为什么这是一个有效的案例。如果A的构造函数为explicit,则从{0}int的自动投放会失败,从而不会产生歧义。

答案 2 :(得分:3)

代码使用GCC8编译好。

这不应该是模棱两可的召唤。对于要调用的B的复制/移动构造函数,然后对于B b({0});,需要执行以下步骤:

  1. 通过A
  2. 0构建A::A(int)
  3. 通过B
  4. 从步骤1中构建的A构建B::B(A)
  5. 通过b的复制/移动构造函数从步骤2中构建的B构造B
  6. 这意味着需要两个用户定义的转换(步骤#1和#2),但在一个隐式转换序列中不允许这样做。