nickelpro
nickelpro

Reputation: 2917

std::variant of a container that contains itself

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?

Upvotes: 3

Views: 1529

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

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.

Upvotes: 4

Related Questions