关于c ++:如何使用std :: array模拟C数组初始化“int arr [] = {e1,e2,e3,…}”的行为?

How to emulate C array initialization “int arr[] = { e1, e2, e3, … }” behaviour with std::array?

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

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)方法可以实现与std::array相同的目标?允许使用宏使其看起来更好。:)

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


我希望有一个简单的make_array

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

(上一次输出时,我使用的是漂亮的打印机。)

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

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

不管怎样,你都需要变异的all_same型特征。这里是从std::is_same归纳出来的(注意,衰减对于允许TT&T const &等的混合很重要):

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

注意,make_array()通过临时文件的副本返回,编译器(有足够的优化标志!)允许将其视为右值或以其他方式进行优化,并且std::array是聚合类型,因此编译器可以自由选择最佳的构造方法。

最后,请注意,当make_array设置初始值设定项时,不能避免复制/移动构造。因此,std::array x{Foo(1), Foo(2)};没有拷贝/移动,但auto x = make_array(Foo(1), Foo(2));有两个拷贝/移动,因为这些参数被转发给make_array。我不认为你能改进这一点,因为你不能在词汇上把变量初始值列表传递给助手,推导类型和大小——如果预处理器有一个用于变量参数的sizeof...函数,也许可以做到,但不能在核心语言中做到。


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

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

事实上,这个make_array的实现列在sizeof…操作人员

C++ 17版

由于类模板建议的模板参数推导,我们可以使用推导指南来除去make_array助手。

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


我知道问这个问题已经很久了,但是我觉得现有的答案仍然有一些缺点,所以我想提出我稍微修改过的版本。以下是我认为一些现有答案缺失的要点。


1。无需依赖RVO

一些答案提到,我们需要依靠RVO返回已构建的array。这不是真的;我们可以使用复制列表初始化来保证不会有临时创建。因此,而不是:

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

我们应该这样做:

1
return {{values}};

2。使make_array成为constexpr功能

这允许我们创建编译时常量数组。

三。无需检查所有参数是否属于同一类型

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

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?

如果我们简单地认为abc具有相同的类型,那么这个检查将失败,但这可能不是我们所期望的。相反,我们应该比较它们的std::decay_t类型(都是int)。

4。通过衰减转发的参数推断数组值类型

这与第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?

我们可能想制作一个array,但是现有答案中的实现可能都没有做到这一点。我们可以做的是,不返回std::array,而是返回std::array, …>

这种方法有一个缺点:我们不能再返回cv限定值类型的array。但大多数时候,我们不使用像array这样的东西,而是使用const array。这是一种权衡,但我认为是合理的。C++ 17 EDOCX1·16也采用这种方法:

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


创建数组生成器类型。

它重载operator,以生成表达式模板,通过引用将每个元素链接到前面的元素。

添加一个finish自由函数,该函数接受数组生成器并直接从引用链生成数组。

语法应该如下所示:

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

它不允许以{}为基础的建设,只有operator=才允许。如果您愿意使用=,我们可以让它工作:

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数量的限制递归使用{}作为子结构。

最后,确实没有一个好的解决方案。

我要做的是编写代码,这样它就可以不区分地同时使用T[]std::array数据——它不关心我向哪个数据馈送数据。有时这意味着我的转发代码必须小心地将[]数组透明地转换为std::array


如果std::array不是一个约束,并且您有boost,那么看看list_of()。这与您想要的C类型数组初始化不同。但是很接近。