Search code examples
c++variant

Is it ok to use std::variant of std::variants


I'm trying to combine two variants into one variant just for readability. This is the code:

using VariantType_basic = std::variant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, std::string>;
using VariantType_vector = std::variant<vector<int8_t>, vector<uint8_t>, vector<int16_t>, vector<uint16_t>, vector<int32_t>, vector<uint32_t>, vector<int64_t>, vector<uint64_t>, vector<std::string>>;
using VariantType_all = std::variant<VariantType_basic, VariantType_vector>;

class Container {
    public:
        Container(){}
        template<typename T>
        T get(string key, bool &found){
            found = false;
            T result;
            auto elem = m_internal_map.find(key);
            if(elem != m_internal_map.end())
                std::visit( 
                    [&](const auto& v){ 
                        // if(holds_alternative<T>(v)){

                        result = std::get<T>(v);
                        found = true;
                    //} 
                },  
                    elem->second 
                    );

            return result;
        }

        template<typename T>
        void put(string key, T&elem){

        }
    private:
        map<string, VariantType_all> m_internal_map;
};

The get() method fails at compile time at result = std::get<T>(v); when I try to do something like this:

Container cont;
bool found;
cont.get<uint16_t>("www", found);

The error message is huge but the first error message is this: /usr/include/c++/7/variant:762:7: error: static assertion failed: T should occur for exactly once in alternatives

Should I stop trying to use variant of variants ?


Solution

  • I suggest to "flatten" the variants instead of having variant of variants:

    template <typename Var1, typename Var2> struct variant_flat;
    
    template <typename ... Ts1, typename ... Ts2>
    struct variant_flat<std::variant<Ts1...>, std::variant<Ts2...>>
    {
        using type = std::variant<Ts1..., Ts2...>;
    };
    
    using VariantType_basic = std::variant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, std::string>;
    using VariantType_vector = std::variant<std::vector<int8_t>, std::vector<uint8_t>, std::vector<int16_t>, std::vector<uint16_t>, std::vector<int32_t>, std::vector<uint32_t>, std::vector<int64_t>, std::vector<uint64_t>, std::vector<std::string>>;
    using VariantType_all = variant_flat<VariantType_basic, VariantType_vector>::type;
    

    And than your class has just to handle one variant level:

    class Container
    {
    public:
        Container(){}
    
        template<typename T>
        T get(const std::string& key, bool &found) const {
            found = false;
            T result;
            auto elem = m_internal_map.find(key);
            if (elem != m_internal_map.end() && std::holds_alternative<T>(elem->second)){
                result = std::get<T>(elem->second);
                found = true;
            }
            return result;
        }
    
        template<typename T>
        void put(const std::string& key, const T& elem) {
            m_internal_map[key] = elem;
        }
    private:
        std::map<std::string, VariantType_all> m_internal_map;
    };
    

    Demo