TL;DR
template<typename T, int N> using raw_array = T[N];
auto &&z = raw_array<int,5>{};
您的auto z = int[5]; 示例与auto z = int; 一样不合法,仅仅是因为类型不是有效的初始化程序。你可以写:auto z = int{}; 因为int{} 是一个有效的初始化器。
一旦意识到这一点,下一次尝试将是:
auto z = int[5]{};
请注意,您的 int y[5] 没有任何初始化程序。如果有,那么您会直接跳到这里。
不幸的是,由于语法晦涩的原因,这也不起作用。相反,您必须找到一种合法的方法来在初始化程序中命名数组类型。例如,可以在初始化程序中使用 typedef 名称。一个方便的可重用模板类型别名消除了对每个数组类型的新 typedef 的繁重要求:
template<typename T, int N> using raw_array = T[N];
auto z = raw_array<int,5>{};
另外:您可以使用模板类型别名来修复 C++ 奇怪的“由内而外”语法,允许您使用 this proposal 以有序、从左到右的方式命名任何复合类型。
不幸的是,由于 C 和 C++ 中的设计错误导致了数组到指针的转换,变量z 的推导类型是int* 而不是int[5]。当临时数组被销毁时,生成的变量会变成一个悬空指针。
C++14 引入decltype(auto),它使用不同的类型推导规则,正确推导数组类型:
decltype(auto) z = raw_array<int,5>{};
但是现在我们遇到了另一个数组设计错误;它们的行为不是正确的对象。您不能使用数组进行分配、复制构造、按值传递等。上面的代码就像是在说:
int g[5] = {};
int h[5] = g;
无论如何,这应该可以工作,但不幸的是 C 和 C++ 中的内置数组 behave bizarrely。在我们的例子中,具体的问题是数组不允许有任何类型的初始化器。它们被严格限制为使用初始化列表。由初始化列表初始化的临时数组本身并不是初始化列表。
答案 1:
此时,Johannes Schaub 提出了一个很好的建议,即我们可以使用临时生命周期延长。
auto &&z = raw_array<int,5>{};
decltype(auto) 不需要,因为添加&& 会更改推导的类型,因此 Johannes Schaub 的建议适用于 C++11。这也避免了数组初始化器的限制,因为我们初始化的是引用而不是数组。
如果您希望数组从初始化程序中推断出它的长度,您可以使用不完整的数组类型:
template<typename T> using unsized_raw_array = T[];
auto &&z = unsized_raw_array<int>{1, 2, 3};
尽管上述方法可以满足您的要求,但您可能更愿意完全避免使用原始数组,因为原始数组的行为不像正确的 C++ 对象,而且它们的行为和上面使用的技术晦涩难懂。
答案2:
C++11 中的 std::array 模板确实像一个适当的对象,包括赋值、可按值传递等,并且在内置数组不具备的情况下通常表现得理智而一致。
auto z = std::array<int,5>{};
但是,这样你就错过了让数组类型从初始值设定项推断其自身长度的机会。相反,您可以编写一个 make_array 模板函数来进行推理。这是一个我没有测试过的非常简单的版本,它并没有做你可能想要的事情,比如验证所有参数都是相同的类型,或者让你显式指定类型。
template<typename... T>
std::array<typename std::common_type<T...>::type, sizeof...(T)>
make_array(T &&...t) {
return {std::forward<T>(t)...};
}
auto z = make_array(1,2,3,4,5);