How to emulate C array initialization “int arr[] = { e1, e2, e3, … }” behaviour with std::array?
(注意:这个问题是不必指定元素的数量,仍然允许直接初始化嵌套类型。)这个问题讨论了像
1 | int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; |
我们不必指定元素的数量,万岁!现在用C++ 11函数对EDCOX1,1,EDCOX1,2,EDCOX1,3,(或您自己的变体)进行迭代,您甚至不需要考虑它的大小。
现在,是否有任何(可能是TMP)方法可以实现与
1 | ??? std_array = {"here","be","elements" }; |
编辑:中间版本,根据各种答案编译,如下所示:
1 2 3 4 5 6 7 8 9 10 11 | #include #include <utility> template<class T, class... Tail, class Elem = typename std::decay<T>::type> std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values) { return { std::forward<T>(head), std::forward<Tail>(values)... }; } // in code auto std_array = make_array(1,2,3,4,5); |
并采用各种酷C++ 11种东西:
- 可变模板
sizeof... - 右值引用
- 完美转发
- 当然是
std::array 。 - 统一初始化
- 用统一初始化省略返回类型
- 类型推理(
auto )
这里有一个例子。
但是,正如@johannes在@xaade答案的注释中指出的那样,不能用这样的函数初始化嵌套类型。例子:
1 2 3 4 5 6 | struct A{ int a; int b; }; // C syntax A arr[] = { {1,2}, {3,4} }; // using std::array ??? std_array = { {1,2}, {3,4} }; |
此外,初始值设定项的数量限制为实现支持的函数和模板参数的数量。
我能想到的最好的是:
1 2 3 4 5 6 7 8 | template<class T, class... Tail> auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)> { std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... }; return a; } auto a = make_array(1, 2, 3); |
但是,这要求编译器执行nrvo,然后跳过返回值的副本(这也是合法的,但不是必需的)。在实践中,我希望任何C++编译器能够优化它,使之与直接初始化一样快。
我希望有一个简单的
1 2 3 4 | template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) { // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } }; return { std::forward<T>(refs)... }; } |
结合前面几篇文章中的一些想法,这里有一个甚至适用于嵌套结构的解决方案(在GCC4.6中测试):
1 2 3 4 5 6 | template <typename T, typename ...Args> std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args) { static_assert(all_same<T, Args...>::value,"make_array() requires all arguments to be of the same type."); // edited in return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...}; } |
奇怪的是,不能使返回值成为右值引用,这对嵌套结构不起作用。不管怎样,这里有一个测试:
1 2 3 4 5 6 7 8 | auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))), make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))), make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))), make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4"))) ); std::cout << q << std::endl; // produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]] |
(上一次输出时,我使用的是漂亮的打印机。)
实际上,让我们来提高这种结构的类型安全性。我们绝对需要所有的类型都是一样的。一种方法是添加一个静态断言,我已经在上面进行了编辑。另一种方法是只在类型相同时启用
1 2 3 4 5 6 | template <typename T, typename ...Args> typename std::enable_if::value, std::array<T, sizeof...(Args) + 1>>::type make_array(T && t, Args &&... args) { return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...}; } |
不管怎样,你都需要变异的
1 2 3 4 5 6 7 8 9 10 | template <typename ...Args> struct all_same { static const bool value = false; }; template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...> { static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value; }; template <typename S, typename T> struct all_same<S, T> { static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value; }; template <typename T> struct all_same<T> { static const bool value = true; }; |
注意,
最后,请注意,当
使用尾随返回语法可以进一步简化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include #include <type_traits> #include <utility> template <typename... T> auto make_array(T&&... t) -> std::array<std::common_type_t<T...>, sizeof...(t)> { return {std::forward<T>(t)...}; } int main() { auto arr = make_array(1, 2, 3, 4, 5); return 0; } |
对于聚合类来说,这是不可调性的,它需要显式的类型规范
1 2 3 4 5 6 7 | /* struct Foo { int a, b; }; */ auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6}); |
事实上,这个
由于类模板建议的模板参数推导,我们可以使用推导指南来除去
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include namespace std { template <typename... T> array(T... t) -> array<std::common_type_t<T...>, sizeof...(t)>; } int main() { std::array a{1, 2, 3, 4}; return 0; } |
在x86-64 GCC 7.0下用
我知道问这个问题已经很久了,但是我觉得现有的答案仍然有一些缺点,所以我想提出我稍微修改过的版本。以下是我认为一些现有答案缺失的要点。
1。无需依赖RVO
一些答案提到,我们需要依靠RVO返回已构建的
1 | return std::array<Type, …>{values}; |
我们应该这样做:
1 | return {{values}}; |
2。使
这允许我们创建编译时常量数组。
三。无需检查所有参数是否属于同一类型首先,如果不是,编译器将发出警告或错误,因为列表初始化不允许缩小范围。其次,即使我们真的决定做我们自己的
1 2 3 4 5 | volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array<int>(a, b, c); // Will this work? |
如果我们简单地认为
这与第3点类似。使用相同的代码段,但这次不要显式指定值类型:
1 2 3 4 5 | volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? |
我们可能想制作一个
这种方法有一个缺点:我们不能再返回cv限定值类型的
1 2 | template< class T > constexpr std::optional<std::decay_t<T>> make_optional( T&& value ); |
考虑到以上几点,在C++ 14中实现EDOCX1 1的完整工作是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include #include <type_traits> #include <utility> template<typename T, typename... Ts> constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)> make_array(T&& t, Ts&&... ts) noexcept(noexcept(std::is_nothrow_constructible< std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&... >::value)) { return {{std::forward<T>(t), std::forward<Ts>(ts)...}}; } template<typename T> constexpr std::array<std::decay<T>_t, 0> make_array() noexcept { return {}; } |
用途:
1 2 3 | constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4,"!"); |
C++ 11将支持(大多数?)的初始化方式。STD容器。
(由@dyp解决)
注意:需要C++ 14(EDCOX1×15)。虽然可以在C++ 11中实现EDOCX1 15。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <iostream> // --- #include #include <utility> template <typename T> using c_array = T[]; template<typename T, size_t N, size_t... Indices> constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) { return std::array<T, N>{{ std::move(src[Indices])... }}; } template<typename T, size_t N> constexpr auto make_array(T (&&src)[N]) { return make_array(std::move(src), std::make_index_sequence<N>{}); } // --- struct Point { int x, y; }; std::ostream& operator<< (std::ostream& os, const Point& p) { return os <<"(" << p.x <<"," << p.y <<")"; } int main() { auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}}); for (auto&& x : xs) { std::cout << x << std::endl; } return 0; } |
创建数组生成器类型。
它重载
添加一个
语法应该如下所示:
1 | auto arr = finish( make_array<T>->* 1,2,3,4,5 ); |
它不允许以
1 | auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} ); |
或
1 | auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] ); |
这些看起来都不是好的解决方案。
使用variardics会限制编译器对varargs和blocks数量的限制递归使用
最后,确实没有一个好的解决方案。
我要做的是编写代码,这样它就可以不区分地同时使用
如果std::array不是一个约束,并且您有boost,那么看看