【问题标题】:How to declare array with auto如何使用 auto 声明数组
【发布时间】:2013-06-05 20:15:40
【问题描述】:

我一直在玩auto,我注意到在大多数情况下,您可以用auto 替换变量定义,然后分配类型。

在下面的代码中wx 是等价的(默认初始化int,但不要进入潜在的副本)。有没有办法声明z 使其具有与y 相同的类型?

int w{};
auto x = int{};
int y[5];
auto z = int[5];

【问题讨论】:

  • 潜在副本很重要。你不能这样做,因为你不能复制数组。
  • 不是“默认初始化的 int”,而是“零初始化的 int”
  • 显然,正如@DavidBrown 所暗示的那样,创建一个临时数组然后复制它是有问题的。因此,即使使用 typedef 作为数组类型也会失败。
  • int 的隐式默认构造函数会进行零初始化,这与未初始化的 int 不同,因为该位置已经存在任何垃圾。
  • @Graznarak:除了它不是“隐式默认构造函数”。 int(例如)的零填充发生在值初始化和静态初始化,但不是默认初始化。

标签: c++ c++11 auto


【解决方案1】:

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) 不需要,因为添加&amp;&amp; 会更改推导的类型,因此 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);

【讨论】:

  • 但我认为他希望它像y 那样未初始化,或者像int{} 那样默认初始化。你是说int[5]{} 会起作用吗?
  • @jxh 是的,你可以有一个默认初始化器:auto z = raw_array&lt;int,100&gt;{};
  • @jxh 这是 gcc 中的一个错误。但是,我发现了一个使我的答案不正确的问题。我必须更新或删除我的答案。
  • 具体来说,虽然raw_array&lt;int,5&gt;{} 确实成功地生成了一个临时数组,但从它推导出来的变量z 的类型实际上是一个指针,指向临时数组。 z 然后在临时销毁时变成一个悬空指针。
  • 可以做的是使用引用的生命周期延长,即auto&amp;&amp;
【解决方案2】:

不太一样,但你可以使用array:

auto z = std::array<int, 5>();

【讨论】:

【解决方案3】:

decltype 与 g++ 4.9.0 20130601 一起使用:

#include <iostream>
#include <algorithm>

static std::ostream& logger = std::clog;

class A {
    static int _counter;
    int _id;
  public:
    A() : _id(++_counter) {
        logger << "\tA #" << _id << " c'tored\n";
    } 

    ~A() {
        //logger << "\tA #" << _id << " d'tor\n";
    } 

    inline int id() const{
        return _id;
    }
};
int A::_counter(0); 

std::ostream& operator<<(std::ostream& os, const A& a) {
    return os << a.id();
}

int main() {

    auto dump = [](const A& a){ logger << a << " ";};

    logger << "x init\n";
    A x[5]; 
    logger << "x contains: "; std::for_each(x, x+5, dump);

    logger << "\ndecltype(x) y init\n";
    decltype(x) y;
    logger << "y contains: ";  std::for_each(y, y+5, dump);
    logger << std::endl;

    return 0;
}

输出:

x init
    A #1 c'tored
    A #2 c'tored
    A #3 c'tored
    A #4 c'tored
    A #5 c'tored
x contains: 1 2 3 4 5 
decltype(x) y init
    A #6 c'tored
    A #7 c'tored
    A #8 c'tored
    A #9 c'tored
    A #10 c'tored
y contains: 6 7 8 9 10 

【讨论】:

    【解决方案4】:

    不完全一样的东西,而且有点丑,但是可​​以从一个list初始化器推导出元素类型,直接声明数组,如下:

    template<typename T>
    struct array_trait
    {
        using element_type = T;
        array_trait(T(&&)[]);
    };
    
    decltype(array_trait({4,5,7}))::element_type a[] = {4,5,7};
    
    std::cout << typeid(a).name() << std::endl;
    
    for (unsigned i = 0; i < 3; i++)
        std::cout << a[i] << std::endl;
    

    类型应为int[3],输出应为4 5 7

    【讨论】:

      【解决方案5】:

      最好考虑一下 c++14 中的 make_something

      #include<iostream>
      #include<experimental/array>
      using namespace std;
      using namespace std::experimental;
      int main()
      {
          auto arr = make_array(1,2,3);
          cout << arr.front() << endl;
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-20
        • 1970-01-01
        • 2019-02-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多