2

I have a binary format that I'm writing encoders and decoders for. Almost all of the binary types directly map to primitives, except for two container types, a list and a map type that can contain any of the other types in the format including themselves.

These feel like they just want to be a typedef of std::variant

typedef std::variant<std::vector<char>, std::vector<int>, ...> ListType

But because I need to be able to contain a vector of ListType itself I end up doing this

struct ListType {
  std::variant<std::vector<char>, std::vector<int>, ..., std::vector<ListType>> value;
}

Which adds a little friction to using the type. There's really no other state to these variables to justify encapsulating them.

Typing it out I realizing I'm asking "Can you forward declare a template?" which seems a stupid question. Still, anyone got a better strategy for this?

nickelpro
  • 2,537
  • 1
  • 19
  • 25

1 Answers1

3
template<class...Ts>
struct self_variant;

template<class...Ts>
using self_variant_base = 
  std::variant<
    std::vector<Ts>...,
    std::vector<self_variant<Ts...>>
  >;


template<class...Ts>
struct self_variant:
  self_variant_base<Ts...>
{
  using self_variant_base<Ts...>::self_variant_base;
  self_variant_base<Ts...> const& base() const { return *this; }
  self_variant_base<Ts...>& base() { return *this; }
};

template<class T>
void print( T const& t ) {
    std::cout << t << ",";
}
template<class T>
void print( std::vector<T> const& v ) {
    std::cout << "[";
    for (auto const& e:v) {
        print(e);
    }
    std::cout << "]\n";
}
template<class...Ts>
void print( self_variant<Ts...> const& sv ) {
    std::visit( [](auto& e){
        print(e);
    }, sv.base());
}

int main() {
    self_variant<int, char> bob = std::vector<int>{1,2,3};
    self_variant<int, char> alice = std::vector<self_variant<int, char>>{ bob, bob, bob };
    print(alice);
}

so, the need for .base() is because std::visit was worded a bit wrong. I believe this will be fixed in a future standard revision.

In any case, this reduces the friction a bit.

Live example, 3 recursive depth live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Unfortunately, I need to contain nested variants of arbitrary nesting depth only known at runtime. That doesn't appear possible here unless I'm missing something? ie. I might need an alice filled with alices, or an alice with alternating bobs and alices. – nickelpro Jun 07 '21 at 03:11
  • 2
    @nickelpro This does exactly that. See https://godbolt.org/z/f4dW3raMr – Yakk - Adam Nevraumont Jun 07 '21 at 03:17
  • @nickelpro to have arbitrary depth you need heap memory. One simple way is to have a `std::unique_ptr` which could hold a variant, as in `std::unique_ptr>`. – Ben Jun 08 '21 at 03:50
  • @Ben unfortunately that makes copying the type rather frustrating and code heavy. I [ended up going with the thin wrapper approach](https://github.com/SpockBotMC/cpp-nbt/blob/master/nbt/nbt.hpp#L94-L103) I laid out in the question, because I needed a solution that would work with `std::map` as well – nickelpro Jun 08 '21 at 04:28
  • @nick I do not understand why you are saying copying is frustrating? The default ctor/assign should work? – Yakk - Adam Nevraumont Jun 08 '21 at 10:06
  • @ben A value ptr is probably a better idea. They are not hard to write. – Yakk - Adam Nevraumont Jun 08 '21 at 10:08