错误C3074:只能使用初始化器列表初始化数组

5

我正在开发一款用于打印PODs、STLs和类似数组的复合类型的小型打印机。在此过程中,我还尝试使用初始化列表,并遇到了以下声明。

std::vector<double[3]> arr{ { 10, 11, 12 }, { 20, 21, 22 } }; 

看起来VC2013和G++ 4.8都不太满意,并且发出了一致的错误消息,但在任何情况下都对我不是很有帮助。

对于VC++:错误 C3074:数组只能用初始化列表初始化

对于G++ 4.8:错误:数组必须使用括号括起来的初始化进行初始化

所以这里不能使用初始化列表,或者我的语法不正确?

在类似的情况下,以下语法似乎是有效的。

std::vector<std::array<int, 3>>  arr{ { 10, 11, 12 }, { 20, 21, 22 } };

我的初始化列表可能存在什么问题?

  • 注意:我知道应该使用std::array而不是C类型数组,但我只是在尝试。
  • 注意:如果您想玩这个,请参考IDEONE版本
  • 注意:此外,如果您能回到标准,那将非常有益。

这可能是那种需要额外一对花括号的情况 arr{{{ 10, 11,12}, {20, 21,22}}} 吗? - Lev Landau
@LevLandau:不,这并没有解决问题。 - Abhijit
根据我的VC2010使用经验,它不喜欢在初始化器中存在多层大括号。尝试移除除最外层括号以外的所有括号。虽然我不记得是否在模板类中使用过它,但这种方式对我有效。 - Logicrat
@Logicrat:其实我正在使用VC2013,它应该支持统一初始化列表。我已经与g ++进行了交叉检查。 - Abhijit
向量需要类型可移动赋值和可移动。 - chris
2个回答

5

阅读当前C++1y草案标准

从表99之前:

如果T能够通过零个或多个参数args从args中进行Emplace构造,则表示以下表达式是符合规范的:allocator_traits::construct(m, p, args)。

表100:

X(il);              |  Equivalent to      | X(il.begin(), il.end());
--------------------+---------------------+--------------------------------
X(i, j);            |                     | Requires:
X a(i, j);          |                     | T shall be EmplaceConstructible
                                          | into X from *i.

因此,std::vector<double[3]> v{ {1,2,3}, {4,5,6} }; 是有效的,当且仅当 double[3] 从作为传递给 std::vector<double[3]> 的初始化列表的元素的 {1,2,3} 中的 EmplaceConstructible

关于前向迭代器也有一个条款,但这没有问题(因为std::initialzier_list 迭代器是前向迭代器)。

std::vector<T> 接受一个 std::initializer_list<T> 参数。

因此,std::initializer_list<double[3]> 是候选列表。

首先,std::initializer_list<double[3]> x = {{1.0, 2.0, 3.0}}; 在gcc中无法编译。但假设这是gcc的一个错误。

其次,::new (nullptr) double[3](std::initializer_list<double>{1.0, 2.0, 3.0}); 是放置 new 操作符,如果没有适当的 construct 覆盖,EmplaceConstructable 将无法编译。因此,double[3] 既不是从 std::initalizer_list<double> 中构建的 EmplaceConstruble,也不是从 double[3] 或其他任何东西中构建的(由于我在放置 new 中使用了括号而导致错误),除非分配器进行了我不知道的魔法以避免放置 new。因此,你的代码违反了当前的草案标准,可能违反了 C++11,并且肯定违反了 C++03(对容器有更严格的要求)。

也许我在这里缺少了一些重要的东西,但是类比地说,如果我编写 std::vector<double[3]> arr; arr[0][0] = 1;,为什么编译器不会抱怨呢? - Abhijit
@Abhijit 未定义行为;编译器可以做任何事情,包括不报错。 - Alan Stokes
1
C++11取消了容器模板参数的全局要求,现在规定需要这些行为的元素的各个容器函数的要求。 - aschepler
@Abhijit的答案已经修订为当前草案标准,而不是我对C++03标准的记忆。 - Yakk - Adam Nevraumont
更直接地说,我认为当 args 是初始化列表时,std::allocator_traits::construct(m, p, args) 不是良好形式的,因为函数模板参数包(class... Args)无法被推断。 - aschepler
显示剩余2条评论

3

这是gcc和MSVC中的一个bug;clang可以正确编译你的代码。

最近版本的gcc实际上会崩溃(“ice”)编译器:

内部编译器错误:树检查:在useless_type_conversion_p处,期望类'type',但有'exceptional'(error_mark),位于tree-ssa.c:1189处

标准相当清晰明了; 来自[dcl.init.list]

5 - 从初始化列表构造类型为std::initializer_list<E>的对象,就好像实现分配了一个类型为EN元素数组,其中N是初始化列表中的元素数。该数组的每个元素都用对应的初始化列表元素进行复制初始化,并且构造了引用该数组的std::initializer_list<E>对象。[...]

从那段落中调整示例:

using E = double[3];
using X = std::vector<E>;
E __a[2] = {{10, 11, 12}, {20, 21, 22}};
X x(__a, __a+2);

这有点作弊,更接近的翻译应该写成E __a[2] = {E{10, 11, 12}, E{20, 21, 22}};,但这是无效的。但是,可以使用大括号初始化列表将double[3]数组进行复制初始化:E __a0 = {10, 11, 12};


这是gcc和MSVC中的一个错误。这是一个记录在案的错误吗?另外,我不太确定[dcl.init.list]是否证明了语法的正确性。此外,我认为仅因为clang可以编译,我们就可以称其为错误(但这只是我的想法,我可能是错的 :-))。 - Abhijit
嗯。如果我们用 E 包装内部的大括号初始化列表,那么我们会得到与 gcc 相同的错误。但是,从大括号初始化列表中复制初始化一个数组 double[3] 是完全可能的:E __a0 = {10, 11, 12} - ecatmur
@Abhijit:Clang也有bug。你不能仅仅因为它在Clang中编译就认为GCC中有bug。 - Lightness Races in Orbit
这不仅仅是从初始化器列表构建 double[3] 数组(尽管这是其中的一步),还需要从 double[3] 进行可插入构造。但我认为这并不是。 - Yakk - Adam Nevraumont
double[3] 甚至不能合法地成为容器的 value_type,因为它甚至不是 Erasable。规范导致伪析构函数调用,这是不好的,因为伪析构函数调用仅允许标量类型。 - T.C.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接