为什么静态大小的数组类型不能是容器类型?

时间:2017-11-24 13:11:20

标签: c++ stl containers

我有一个静态大小的数组的别名,使用它很简单直接:

using triplet_t = std::uint8_t[3];

//           vvvvvvvvvvvvvvvvvv <--- easier than std::uint8_t(&triplet)[3]
void f(const triplet_t &triplet) { /* whatever */ }

triplet_t t{}; // As good as std::uint8_t t[3]{};

t[0] = '0';
t[1] = '1';
t[2] = '2';
for (auto &v : t) std::cout << v << ' ';
std::cout << '\n';

// So far so good...
triplet_t t3[3]{};
for (auto &r : t3)
    for(auto &v : r)
        v = 42;

我甚至可以在容器中使用别名:

std::vector<triplet_t> vt;

或者我以前认为,因为只要你使用vt它就会失败:

vt.push_back({});
  

GCC 8.0.0 201711

error: parenthesized initializer in array new [-fpermissive]
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error: request for member '~unsigned char [3]' in '* __p', which is of non-class type 'unsigned char [3]'
destroy(_Up* __p) { __p->~_Up(); }
                    ~~~~~~^~~

问题似乎是在展开所有模板技巧后,一个placement-new被称为转发所有提供括号的参数,显然这不是初始化静态大小数组的方法。

此外,容器以某种方式将triplet_t视为对象,因此要求析构函数,再次无法编译。没有别名,问题显然是一样的:

std::vector<std::uint8_t[3]> vt;
vt.push_back({});          // Boom!
vt.push_back({255, 0, 0}); // Ouch!

但使用具有相同内存布局的struct没问题:

struct rgb { std::uint8_t r, g, b; };
std::vector<rgb> vt;
vt.push_back({});          // Nice!
vt.push_back({255, 0, 0}); // Cool!

我想知道为什么会发生这种情况,有没有办法在容器中使用静态大小的数组作为包含类型?

1 个答案:

答案 0 :(得分:4)

根据documentationpush_back要求值类型为CopyInsertableMoveInsertable。我们来看看definition

  

如果 T 是MoveInsertable,则类型T CopyInsertable 进入容器 X ,其value_type与 T 相同进入X,并且,给定[...]以下表达式是格式良好的:

std::allocator_traits<A>::construct(m, p, v);

因此,在C数组的情况下,对于标准分配器,有一个表达式like

::new((void *)p) int[3](std::forward<int[3]>(v))

其中v是数组类型。根据{{​​3}}:

,这是不正确的
  
      
  • 如果type是数组类型,则初始化对象数组。      
        
    • 如果缺少初始化程序,则每个元素都是默认初始化的
    •   
    • 如果初始化程序是一对空括号,则每个元素都是值初始化的。
    •   
    • 如果初始化程序是括号括起来的参数列表,则数组是聚合初始化的。
    •   
  •   

没有允许非空的parantheses的数组类型的语法。

论证与MoveInsertable类别非常类似。 总而言之,提出的解决方案是(已经提到的)std::array的使用,它本身不是数组类型,因此可以通过标准分配器'construct函数采用的语法正确初始化。 / p>

最后一点:严格别名规则仅允许使用unsigned charsigned charchar进行任何类型的转化。虽然几乎可以肯定std::uint8_t只是你实现中其中一个的别名typedef,但标准中并不能保证这一点。