如何模拟C数组初始化“int arr = {e1,e2,e3,…}”行为与std :: array?

(注意:这个问题是关于不必指定元素的数量,仍然允许直接初始化嵌套types。)
这个问题讨论了C数组的用法,比如int arr[20]; 。 在他的回答中 ,@James Kanze展示了C数组的最后一个据点之一,它是独特的初始化特性:

 int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; 

我们不必指定元素的数量,万岁! 现在用<iterator> ( 或你自己的变体 )的C ++ 11函数std::beginstd::end <iterator> ,你甚至不需要考虑它的大小。

现在,有没有(可能是TMP)的方式来实现相同的std::array ? 使用macros可以使它看起来更好。 🙂

 ??? std_array = { "here", "be", "elements" }; 

编辑 :从各种答案编译的中级版本,看起来像这样:

 #include <array> #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 ,当然
  • 统一初始化
  • 省略了统一初始化的返回types
  • types推断( auto

在这里可以find一个例子。

然而 ,正如@Johannes在@ Xaade的回答中所指出的那样,你不能用这样的函数初始化嵌套types。 例:

 struct A{ int a; int b; }; // C syntax A arr[] = { {1,2}, {3,4} }; // using std::array ??? std_array = { {1,2}, {3,4} }; 

此外,初始化程序的数量仅限于实现所支持的函数和模板参数的数量。

我能想到的最好的是:

 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 ++编译器都能够优化,使其像直接初始化一样快。

我期望一个简单的make_array

 template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) { return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } }; } 

结合以前文章的一些想法,下面是一个解决scheme,即使是嵌套的构造(在GCC4.6中testing):

 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)...}; } 

奇怪的是,可以不能使返回值为右值引用,这将不适用于嵌套的结构。 无论如何,这是一个testing:

 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]]] 

(对于最后一个输出,我正在使用我的漂亮打印机 。)


实际上,让我们改进这种结构的型式安全。 我们绝对需要所有types都是一样的。 一种方法是添加一个静态断言,我已经在上面编辑了。 另一种方法是只在types相同时才启用make_array ,如下所示:

 template <typename T, typename ...Args> typename std::enable_if<all_same<T, Args...>::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)...}; } 

无论哪种方式,您都需要可变的all_same<Args...>types特征。 在这里,从std::is_same<S, T>概括(注意,衰减对于TT&T const &等的混合是重要的):

 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; }; 

请注意, make_array()通过临时副本(编译器(具有足够的优化标记!))被允许视为右值或以其他方式进行优化来返回,而std::array是聚合types,因此编译器是免费的挑选最好的施工方法。

最后,请注意,当make_array设置初始化程序时,您无法避免复制/移动构造。 所以std::array<Foo,2> x{Foo(1), Foo(2)}; 没有复制/移动,但auto x = make_array(Foo(1), Foo(2)); 有两个副本/移动作为参数转发到make_array 。 我不认为你可以改善这一点,因为你不能将一个可变的初始化列表词法地传递给助手, 推断出types和大小 – 如果预处理器有一个sizeof...函数的可变参数,也许这可能是完成,但不在核心语言内。

std :: array不能被初始化为一个C数组的事实很差。 几个月前,我在comp.lang.c ++中做了一个关于这个的post,在这里提供了一个make_array解决scheme。

感兴趣的链接: http : //groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/f1a5a1451b003bec

像make_array这样的函数都很好,但是你不应该使用一个复杂的函数来初始化一些像数组一样基本的东西。 这是另一个给C ++带来坏名声的领域。 如果一个语言不能初始化一个数组,那么就会显示一个可怕的情况。

像下面的例子应该只是工作! 没有更多的废话请!

 std::array <int> arr = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; //automatically deduces its size from the initializer list :) 

C ++ 11将支持(大多数?)std容器的这种初始化方式 。

这仍然不是一个真正的解决scheme。 C风格的数组结合了两个重要特性:初始化器的静态初始化自动计数。 有可能使用可变参数模板参数的解决scheme,但初始化(至less在我的情况下)通常是string文字,这是模板不可接受的参数; 而且大多数情况下,它是一个struct数组; 一个典型的例子会是这样的:

 struct Table { char const* key; int value; }; static Table const table[] = { ... }; 

我实际上有一个类模板来定义这些types的结构。 除了数据成员之外,它还创build一个用于匹配的成员函数对象和一个使用它的静态成员函数find 。 但是类模板仍然没有定义表格; 只是它的一个元素。 所以你最终写的东西是这样的:

 StaticMap<int> const table[] = { { "first key", 1 }, { "second key", 2 }, // ... }; 

然后:

 StaticMap<int> const* entry = StaticMap<int>::find( key ); 

做仰视。 我真的想把它们全部包装在一个模板中,但我还没有find解决scheme。 也许与variadic模板参数,但离手,我没有看到一个解决scheme,仍然会保持完全静态初始化(这避免了任何初始化问题的顺序)。

(由@dyp解决)

注意:需要C ++ 14std::index_sequence )。 尽pipe可以在C ++ 11中实现std::index_sequence

 #include <iostream> // --- #include <array> #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 << "(" << px << "," << py << ")"; } 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.不需要依靠RVO

有些答案提到我们需要依靠RVO来返回构造的array 。 那是不正确的; 我们可以利用复制列表初始化来保证永远不会有临时创build。 所以,而不是:

 return std::array<Type, …>{values}; 

我们应该这样做:

 return {{values}}; 

2. make_array是一个constexpr函数

这使我们可以创build编译时常量数组。

3.不需要检查所有的参数是相同的types

首先,如果不是,编译器会发出警告或错误,因为列表初始化不允许缩小。 其次,即使我们真的决定做自己的static_assert事情(也许提供更好的错误信息),我们仍然应该比较参数的衰减types而不是原始types。 例如,

 volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array<int>(a, b, c); // Will this work? 

如果我们简单地static_assert abc具有相同的types,那么这个检查将会失败,但这可能不是我们所期望的。 相反,我们应该比较它们的std::decay_t<T>types(这些都是int s))。

4.通过衰减转发的参数来推导数组值types

这与第3点类似。使用相同的代码片段,但是这次不显式指定值types:

 volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? 

我们可能想要一个array<int, 3> ,但现有答案中的实现可能都不能做到这一点。 我们可以做的是,不是返回一个std::array<T, …> ,而是返回一个std::array<std::decay_t<T>, …>

这种方法有一个缺点:我们不能再返回一个cv-qualified值types的array 。 但是大多数情况下,不是像array<const int, …>这样的array<const int, …> ,而是使用const array<int, …> 。 有一个权衡,但我认为是一个合理的。 C ++ 17 std::make_optional也采用这种方法:

 template< class T > constexpr std::optional<std::decay_t<T>> make_optional( T&& value ); 

考虑到以上几点,在C ++ 14中make_array的完整工作实现如下所示:

 #include <array> #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 {}; } 

用法:

 constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4, "!"); 

使用尾随返回语法make_array可以进一步简化

 #include <array> #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; } 

不幸的是,对于聚合类,它需要明确的types规范

 /* struct Foo { int a, b; }; */ auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6}); 

实际上,这个make_array实现在sizeof …运算符中列出


C ++ 17版本

感谢模板参数推导类模板的build议,我们可以使用演绎指南摆脱make_array帮手

 #include <array> 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下用-std=c++1z标志编译

如果std :: array不是一个约束,并且你有Boost,那么看看list_of() 。 这不像你想要的Ctypes数组初始化。 但接近。

创build一个数组制造者types。

它重载operator,生成一个expression式模板链接每个元素到以前的引用。

添加一个finish免费的函数,该函数接受数组制造商,并直接从引用链生成一个数组。

语法应该看起来像这样:

 auto arr = finish( make_array<T>->* 1,2,3,4,5 ); 

它不允许以{}基础的构造,因为只有operator= 。 如果你愿意使用=我们可以得到它的工作:

 auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} ); 

要么

 auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] ); 

这些都不是好的解决scheme。

使用variardics限制了编译器对可变参数数量的限制,并为子结构块recursion使用{}

最后,真的没有一个好的解决scheme。

我所做的是我写我的代码,所以它消耗T[]std::array数据agnostically – 它并不关心我喂它。 有时这意味着我的转发代码必须仔细地将[]数组透明地转换成std::array