Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why static-sized array type cannot be a container type?

I have an alias to a static-sized array, using it is easy and straightforward:

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;

I can even use the alias in containers:

std::vector<triplet_t> vt;

Or so I used to think, because as soon as you use vt it fails:

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(); }
                    ~~~~~~^~~

The issue seems to be that after unrolling all the template trickery a placement-new is called forwarding all the parameters provided parenthesized, and obviously this is not the way to initialize a static-sized array.

Also, somehow the container thinks of triplet_t as an object and hence is asking for a destructor, failing again to compile. The issue is obviously the same without the alias:

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

But no problem using a struct with the same memory layout:

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

I wonder why this happens, is there a way to use static-sized arrays as contained type in containers?

like image 750
PaperBirdMaster Avatar asked Jan 20 '26 15:01

PaperBirdMaster


1 Answers

Reading std::vector documentaion, you can find that T must meet the requirements of CopyAssignable and CopyConstructible.

This means (simplifying): with v and t two instances of type T, the expression t = v must be legal. Clearly, if T is a native array, this is not the case (you cannot assign a C-array to another), and certain functions of std::vector<T> would be ill-formed.

A solution would be to define triplet_t as:

using triplet_t = std::array<std::uint8_t, 3>;

void f(const triplet_t &triplet) { /* whatever */ }

triplet_t t{};

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.push_back({});
like image 145
YSC Avatar answered Jan 22 '26 06:01

YSC



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!