1

So I'm working with this struct type with variable length array member, like this:

struct Entry;

struct Data {
    int members;
    size_t entries_size;
    Entry entries[1];
};

The entries_size member is the actual size of the entires array, it is only known when read from network. The problem is that I'd like to use it in a std::variant among other network data types, how can I put it into a std::variant with the footprint of the whole data object, without dangling pointers flying around?

std::variant<Data, OtherData2, OhterData3...> v;
fluter
  • 13,238
  • 8
  • 62
  • 100
  • 3
    That is not a variable length array, but a flexible array member from C. It is not allowed in C++. (And variable length arrays are not a part of standard C++, though some compilers provide them as an extension.) The C++ approach to your struct would be to use a std::vector in place of the entries_size and entries members. – Avi Berger Aug 30 '22 at 01:13
  • Flexible array members like that are not part of valid C++, so you can't expect to have one as a member of a variant (except maybe with particular compilers, as a non-standard extension). Why not have a `std::vector` (which can be resized) as member of your struct or of the variant? – Peter Aug 30 '22 at 01:15
  • The problem is that the decoding works just by overlying the struct to the network buffer, because all data are POD types, but `std::vector` is not a POD type, overlaying would not work. – fluter Aug 30 '22 at 01:16
  • 2
    Then you need to do a bit more work to serialise/deserialise your objects. A simple overlay like you seek won't work, even if your compiler *does* support flexible array members - since, somehow, the networking functions needs to be informed about how much data they are required to carry. Even if you output the length then contents of the buffer, the receiving code will need to reconstruct that anyway. – Peter Aug 30 '22 at 01:18
  • 1
    I just want to add that even in C this is not using the feature correctly. A flexible array member must have no size specified, i.e. it should be `Entry entries[];` (and then the compiler would also probably warn you that this is not valid C++). – user17732522 Aug 30 '22 at 05:45

2 Answers2

6

This cannot be done in C++, C++ simply does not work this way on a fundamental level.

In C++ all objects have a defined type, and a defined, fixed size. You are using a common C-like approach of manually over-allocating memory so that there can be additional Entrys in memory, after the pro-forma one.

Taking specific, strict, well-controlled steps allows this to be done without breaking (most) "the rules" (but only assuming that Entry is trivial, of course, which is unclear).

However introducing a variant into the mix is pretty much a showstopper. A std::variant is (certainly) not a trivial POD. This particular showstopper can, potentially, be worked around using placement new after over-allocating a calculated amount of memory for this kind of a franken-variant.

However once past this hurdle the next one is insurmountable: C++ gives you no guarantees, whatsoever, as to a variant's internal layout. After all, a variant must keep track of which variant value is constructed, and the additional metadata for all of that can appear either before or after the reserved space for the value of the variant. It is not specified. Game over.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Game over, indeed. Thanks for putting it so bluntly! :) – bitmask Aug 30 '22 at 01:29
  • @Sam: "*A std::variant is (certainly) not a trivial POD.*" Actually, it is required to be a trivially copyable type if all of the `T`s are themselves trivially copyable. But it isn't required to be standard layout, so it isn't a POD. – Nicol Bolas Aug 30 '22 at 01:43
3

As has been pointed out, this is not legal C++. It just wont work this way. However, if the amount of entries is bound, it might be possible to do this:

template <std::size_t N>
struct Data {
    int members;
    static constexpr std::size_t entries_size = N;
    Entry entries[N];
}; // Of couse, you might want to use std::array<Entry,N> instead!

// ...
std::variant<Data<2>, Data<8>, Data<32>> example;

This, of course, is highly situational. If you know the incoming data will fall into either 2, 8 or 32 entries, you can specify these at compile time. If the amount of entries is completely variable, with no guarantees, use std::vector. That's what it's for.

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • "use std::vector. That's what it's for." Thanks so much, @bitmask. It's late in the day for me and I didn't think of that obvious solution before I Googled. – Erik Mar 29 '23 at 14:32