Search code examples
c++stlc++17variant

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?


Solution

  • 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.