考虑以下代码:
这个程序符合C++11标准吗?在我的N3797副本中,它说
8.5.4 列表初始化[dcl.init.list]
3: 对于类型T的对象或引用,列表初始化定义如下:
- 如果T是一个聚合体,则执行聚合初始化(8.5.1)。
- 否则,如果T是std::initializer_list<E>的特例...
- 否则,如果T是类类型,则考虑构造函数。 枚举适用的构造函数,并使用重载解析选择最佳构造函数。 如果需要将任何类型进行缩小转换,则程序是非法的。
- 否则,如果初始化列表具有类型为E的单个元素,并且T不是引用类型或与E相关联,则从该元素初始化对象或引用; 如果需要将该元素转换为T进行缩小转换,则程序是非法的。
- 否则,如果T是引用类型,则将指向T引用的pr-value临时对象进行复制列表初始化或直接列表初始化,具体取决于引用的初始化方式,并将引用绑定到该临时对象上。
- 否则,如果初始化列表没有元素,则对对象进行值初始化。
- 否则,程序是非法的。
并且提示“无法将A转换为int”或类似的错误,因为聚合初始化失败。在gcc 5.4上,它在C++11标准下工作正常。
在clang上,您会得到类似的错误,例如clang-3.5、3.6,并且在clang-3.7开始工作。
我知道在C++14标准下它是规范的,并且在一个缺陷报告中提到了这一点here。
然而,我不明白为什么这被认为是标准中的缺陷。
当标准写道,
“如果X,则执行foo初始化。否则,如果Y,则执行bar初始化……否则,程序不符合规范。”
这难道不意味着,如果X成立,但无法执行foo初始化,那么我们应该检查Y是否成立,然后尝试bar初始化吗?
这将使示例工作,因为当聚合初始化失败时,我们不匹配
所有编译器都会按照C++11和C++14标准处理这个例子。但是,似乎CWG缺陷记录中修改的措辞并不适用于此情况。它的原话是:“如果T是类类型,并且初始化列表有一个类型为cv T或从T派生的类类型的单个元素,则对象从该元素初始化。”
在
struct A {
int x;
};
int main() {
A a;
A b{a};
}
这个程序符合C++11标准吗?在我的N3797副本中,它说
8.5.4 列表初始化[dcl.init.list]
3: 对于类型T的对象或引用,列表初始化定义如下:
- 如果T是一个聚合体,则执行聚合初始化(8.5.1)。
- 否则,如果T是std::initializer_list<E>的特例...
- 否则,如果T是类类型,则考虑构造函数。 枚举适用的构造函数,并使用重载解析选择最佳构造函数。 如果需要将任何类型进行缩小转换,则程序是非法的。
- 否则,如果初始化列表具有类型为E的单个元素,并且T不是引用类型或与E相关联,则从该元素初始化对象或引用; 如果需要将该元素转换为T进行缩小转换,则程序是非法的。
- 否则,如果T是引用类型,则将指向T引用的pr-value临时对象进行复制列表初始化或直接列表初始化,具体取决于引用的初始化方式,并将引用绑定到该临时对象上。
- 否则,如果初始化列表没有元素,则对对象进行值初始化。
- 否则,程序是非法的。
这个例子的重点是,类型是一个聚合体,但列表初始化应该调用复制构造函数。在C++11标准下,在gcc 4.8
和gcc 4.9
上失败:
main.cpp: In function ‘int main()’:
main.cpp:7:8: error: cannot convert ‘A’ to ‘int’ in initialization
A b{a};
^
并且提示“无法将A转换为int”或类似的错误,因为聚合初始化失败。在gcc 5.4上,它在C++11标准下工作正常。
在clang上,您会得到类似的错误,例如clang-3.5、3.6,并且在clang-3.7开始工作。
我知道在C++14标准下它是规范的,并且在一个缺陷报告中提到了这一点here。
然而,我不明白为什么这被认为是标准中的缺陷。
当标准写道,
“如果X,则执行foo初始化。否则,如果Y,则执行bar初始化……否则,程序不符合规范。”
这难道不意味着,如果X成立,但无法执行foo初始化,那么我们应该检查Y是否成立,然后尝试bar初始化吗?
这将使示例工作,因为当聚合初始化失败时,我们不匹配
std :: initializer_list
,下一个条件是“ T
是类类型”,然后我们考虑构造函数。
请注意,这似乎是在这个修改后的示例中如何工作的。
struct A {
int x;
};
int main() {
A a;
const A & ref;
A b{ref};
}
所有编译器都会按照C++11和C++14标准处理这个例子。但是,似乎CWG缺陷记录中修改的措辞并不适用于此情况。它的原话是:“如果T是类类型,并且初始化列表有一个类型为cv T或从T派生的类类型的单个元素,则对象从该元素初始化。”
http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1467
但在第二个代码示例中,初始化列表在技术上包含const T&
。所以我不明白,除非聚合初始化失败后,我们应该尝试构造函数,否则它怎么能工作。
我错了吗?聚合初始化失败后不应该尝试构造函数吗?
这里是一个相关的例子:
#include <iostream>
struct B {
int x;
operator int() const { return 2; }
};
int main() {
B b{1};
B c{b};
std::cout << c.x << std::endl;
}
在
clang-3.6
, gcc-4.8
, gcc-4.9
中,它会打印 2
,而在 clang-3.7
, gcc-5.0
中,它会打印 1
。假设我错了,并且在 C++11 标准中,聚合体的列表初始化应该是聚合初始化而不是其他任何内容,直到缺陷报告中的新措辞被引入,那么当我在新编译器上选择 -std=c++11
时,这种情况是否是一个错误?
struct B
示例中,当使用std=c++11
时,gcc-5
应该与gcc-4.8
和gcc-4.9
表现相同吗? - Chris Beckstd=c++11
,我通常认为他们正在尝试实现 N3797。否则,除非我阅读所有标准,否则我无法真正知道编译器将尝试做什么。或者我必须告诉人们:“您不能在版本3.6之后使用clang,否则它将编译但初始化不会按预期工作,您将看到1而不是2”。 - Chris Beck--std=c++11
时,这意味着 C++11 标准文档中描述的 C++,再加上任何澄清标准委员会意图的缺陷报告?或者类似这样的东西? - Chris BeckA b{a};
只会在旧编译器中失败,除非涉及这些古怪的隐式转换。也许如果聚合体包含对自身的引用之类的东西,但那也相当人为。 - Chris Beck