请考虑以下事项:
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)
}
brace
中(4)
的构建清楚而明确地称(2)
。在clang上,paren
中(3)
的构造明确地调用了(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
,所以这样不应该允许隐式构造。
答案 0 :(得分:8)
据我所知,这是一个铿锵的错误。
复制列表初始化具有相当不直观的行为:它将显式构造函数视为可行,直到重载完成完成,但是如果选择了显式构造函数,则可以拒绝重载结果。 N4567后草案中的措辞,[over.match.list] p1
在copy-list-initialization中,如果选择
explicit
构造函数,则为 初始化是不正确的。 [注意:这与其他不同 情境(13.3.1.3,13.3.1.4),其中只转换构造函数 被认为是复制初始化。此限制仅适用 如果这个初始化是最终过载结果的一部分 解析度。 - 结束记录]
clang HEAD接受以下程序:
#include <iostream>
using namespace std;
struct String1 {
explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
String2(const char*) { cout << "String2\n"; }
};
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"} );
}
除了评论f1
的电话之外,直接来自Bjarne Stroustrup的N2532 - Uniform initialization,第4章。感谢Johannes Schaub在std-discussion上向我展示了这篇论文
同一章包含以下说明:
explicit
的真正优势在于它呈现f1("asdf")
错误。一个问题是重载决策“更喜欢”非 -explicit
构造函数,以便f("asdf")
调用f(String2)
。我考虑一下f("asdf")
的分辨率低于理想因为作者String2
可能并不意味着解决有利于的歧义String2
(至少在每种情况下都不是明确的和非显式的 构造函数就像这样)和String1
的作者当然 没有。该规则支持不使用explicit
的“草率程序员”。
据我所知,N2640 - Initializer Lists — Alternative Mechanism and Rationale是最后一篇论文,其中包含了这种超载解决方案的基本原理;它的继任者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.
第二种方法是在查看时忽略显式构造函数 为了隐式转换的可行性,但在包含它们时 实际上选择转换构造函数:如果是显式的 构造函数最终被选中,程序格式不正确。这个 替代方法允许最后一个(Pixel-vs-Matrix)示例工作 按预期(
transpose(Pixel)
被选中),同时制作 原始例子(“X x4 = { 10 };
”)形成不良。
虽然本文提议使用第二种方法,但其措辞似乎存在缺陷 - 在我对措辞的解释中,它不会产生本文理论部分所概述的行为。在N2672中对措辞进行了修订以使用第一种方法,但我找不到任何有关为何更改的讨论。
当然,在OP中初始化变量时涉及的措辞略多,但考虑到我的答案中第一个示例程序中clang和gcc之间行为的差异是相同的,我认为这涵盖了要点。
答案 1 :(得分:0)
即使评论太长,这也不是一个完整的答案 我会尝试为你的推理提出一个反例,我已经准备好了,因为我还远未确定。 无论如何,让我们试试!! : - )
它遵循简化的例子:
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
临时对象,作为复制/移动构造函数的参数后立即使用,并且由于涉及的构造函数不明确而再次被允许
后者将解释第二和第三位候选人。
有意义吗?
只要您在我的推理中解释我的错误,我就准备删除答案。 : - )