考虑以下内容:
struct A {
A(int, int) { }
};
struct B {
B(A ) { } // (1)
explicit B(int, int ) { } // (2)
};
int main() {
B paren({1, 2}); // (3)
B brace{1, 2}; // (4)
}
CCD_ 1在CCD_ 2中的构造清楚而明确地调用CCD_。在clang上,(3)
中paren
的构造明确地调用(1)
,其中与gcc 5.2一样,它无法使用:进行编译
main.cpp: In function 'int main()':
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
B paren({1, 2});
^
main.cpp:6:5: note: candidate: B::B(A)
B(A ) { }
^
main.cpp:5:8: note: candidate: constexpr B::B(const B&)
struct B {
^
main.cpp:5:8: note: candidate: constexpr B::B(B&&)
哪个编译器是对的?我怀疑clang在这里是正确的,因为gcc中的模糊性只能通过隐式构造B{1,2}
并将其传递给复制/移动构造函数的路径出现,但该构造函数被标记为explicit
,因此不应该允许这种隐式构造。
据我所知,这是一个clang bug。
复制列表初始化有一个相当不直观的行为:它认为显式构造函数在重载解析完全完成之前是可行的,但如果选择了显式构造函数,则可以拒绝重载结果。N4567后草案中的措辞,[超过匹配列表]p1
在副本列表初始化中,如果选择了
explicit
构造函数,则初始化格式不正确。[注意:这与其他情况(13.3.1.3、13.3.1.4),其中仅转换构造函数被考虑用于复制初始化。此限制仅适用如果此初始化是过载最终结果的一部分决议。--尾注]
clang HEAD接受以下程序:
#include <iostream>
using namespace std;
struct String1 {
explicit String1(const char*) { cout << "String1n"; }
};
struct String2 {
String2(const char*) { cout << "String2n"; }
};
void f1(String1) { cout << "f1(String1)n"; }
void f2(String2) { cout << "f2(String2)n"; }
void f(String1) { cout << "f(String1)n"; }
void f(String2) { cout << "f(String2)n"; }
int main()
{
//f1( {"asdf"} );
f2( {"asdf"} );
f( {"asdf"} );
}
这是,除了注释掉对brace
0的调用之外,直接来自Bjarne Stroustrup的N2532-统一初始化,第4章。感谢Johannes Schaub给我看了这篇关于性病讨论的论文。
同一章包含以下解释:
CCD_ 11的真正优点是它使CCD_错误一个问题是过载解决方案"偏好"非
explicit
构造函数,使得f("asdf")
调用f(String2)
。我认为CCD_ 16的分辨率不理想是因为作者String2
可能不是为了解决歧义而支持String2
(至少不是在所有显式和非显式的情况下构造函数是这样出现的)和String1
的作者没有。该规则支持那些不使用(4)
0的"草率程序员"。
据我所知,N2640——初始化器列表——替代机制和原理是最后一篇包含这种过载解决原理的论文;它的继任者N2672被投票纳入C++11草案。
从其章节"显式的意义":
使示例格式不正确的第一种方法是要求构造函数(显式和非显式)被认为是隐式的转换,但如果最终选择了显式构造函数,那个程序格式不正确。这条规则可能会带来自己的惊喜;例如:
struct Matrix { explicit Matrix(int n, int n); }; Matrix transpose(Matrix); struct Pixel { Pixel(int row, int col); }; Pixel transpose(Pixel); Pixel p = transpose({x, y}); // Error.
第二种方法是在查找时忽略显式构造函数隐式转换的可行性,但在实际选择转换构造函数:如果显式构造函数最终被选中,程序格式不正确。这另一种方法允许最后一个(像素与矩阵)示例工作如预期(选择
transpose(Pixel)
),同时原始示例("X x4 = { 10 };
")格式错误。
虽然本文建议使用第二种方法,但其措辞似乎有缺陷——在我对措辞的解释中,它没有产生论文基本原理部分概述的行为。N2672中的措辞被修改为使用第一种方法,但我找不到任何关于为什么要更改的讨论。
当然,与OP中一样,初始化变量所涉及的措辞稍多,但考虑到clang和gcc之间的行为差异与我回答中的第一个示例程序相同,我认为这涵盖了要点。
这不是一个完整的答案,尽管它作为注释太长了
我会尝试为你的推理提出一个反例,我准备投反对票,因为我还远不能确定
不管怎样,让我们试试!!:-)
它遵循简化的示例:
struct A {
A(int, int) { }
};
struct B {
B(A) { }
explicit B(int, int ) { }
};
int main() {
B paren({1, 2});
}
在这种情况下,声明{1, 2}
显然给出了两个解决方案:
通过
B(A)
直接初始化,因为A(int, int)
不是显式的,因此它是允许的,这实际上是第一个候选出于同样的原因,它可以被解释为
B{B(A{1,2})}
(好吧,让我滥用这个符号来给你一个想法和我的意思),也就是说,{1,2}
允许构造一个B
临时对象,该对象在之后立即用作复制/移动构造函数的参数,并且它再次被允许,因为所涉及的构造函数不是显式
后者将解释第二个和第三个候选人。
这有道理吗
只要你向我解释我的推理错误,我就准备删除答案。:-)