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&&)
,为什么在这种情况下它是不明确的?
答案 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.best.ics]将参数表达式转换为引用类型所需的转换序列。从概念上讲,此转换序列对应于使用参数表达式复制初始化引用类型的临时值。顶级cv资格的任何差异都归入初始化本身,并不构成转换。
转换为:
B b = {0};
再次关注[over.ics.list]/p6:
允许用户定义的转换将初始化列表元素转换为构造函数参数类型[...]
允许编译器使用用户定义的转换:
A(int);
将参数0
转换为B
的构造函数参数A
。
对于候选人#3,同样的推理适用于#2。最终,编译器无法在上述隐式转换序列 {citation needed} 之间进行选择,并报告歧义。
答案 1 :(得分:3)
B b({0})
可能会导致对以下任一情况的调用:
B::B(A)
复制B
的构造函数:从B
构建临时{0}
对象,
然后将其复制到b
。
因此含糊不清。
如果你调用B b{0}
,它可以解决,它直接使用定义的构造函数而不涉及复制构造函数。
修改强>
关于第2点的有效性:
B
有一个接受A
的构造函数。现在,A
可以由int
构建。此外,int
可以通过初始化列表构建。这就是为什么这是一个有效的案例。如果A
的构造函数为explicit
,则从{0}
到int
的自动投放会失败,从而不会产生歧义。
答案 2 :(得分:3)
代码使用GCC8编译好。
这不应该是模棱两可的召唤。对于要调用的B
的复制/移动构造函数,然后对于B b({0});
,需要执行以下步骤:
A
0
构建A::A(int)
B
A
构建B::B(A)
b
的复制/移动构造函数从步骤2中构建的B
构造B
。这意味着需要两个用户定义的转换(步骤#1和#2),但在一个隐式转换序列中不允许这样做。