Reputation: 725
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 ?
Upvotes: 0
Views: 2544
Reputation: 217085
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;
};
Upvotes: 7
Reputation: 15943
The problem is that std::visit
will instantiate calls to your lambda for all variant types at compiletime and then just picks the one corresponding to the actual value contained within your variant at runtime. With the way the lambda is currently written, this means that it'll try to instantiate
result = std::get<T>(v);
for your given T
with both, a version of the lambda for the case of VariantType_basic
as well as for the case of VariantType_vector
. Since your VariantType_vector
does not at all contain a std::uint16_t
, this second instantiation will fail to compile with the given error since you're attempting to call std::get<T>
on a type that does not contain T
in the list of alternatives at all…
One way to solve this problem would be to write your visitor lambda such that the code that calls std::get<T>
is only instantiated for variant values that are variants that actually may contain a value of the type you're looking for:
template <typename T, typename V>
constexpr bool has_variant = false;
template <typename T, typename... Types>
constexpr bool has_variant<T, std::variant<Types...>> = (std::is_same_v<T, Types> || ...);
template <typename T, typename V>
constexpr bool has_variant<T, V&> = has_variant<T, V>;
and then
std::visit([&](const auto& v)
{
if constexpr (has_variant<decltype(v), T>)
{
result = std::get<T>(v);
found = true;
}
else
found = false;
}, elem->second);
All that being said, this whole construction appears to me a pretty brittle solution for a problem that can probably be solved better in a different way. I would recommend to reconsider your approach here…
Upvotes: 2
Reputation: 36379
You can use a second level of visit
to descend into your nested variant:
template < typename T >
struct Getter
{
T& value;
bool& found;
void operator()(const T& t)
{
value = t;
found = true;
}
template < typename U >
void operator()(const U& u) {}
};
if(elem != m_internal_map.end()) {
Getter< T > getter = { result, found };
std::visit( getter, elem->second );
}
If the type isn't the desired type then Getter
will ignore it. Note that Getter
will also capture values convertible to T
so might not be what you want.
Using a single level of variant would definitely be simpler.
Upvotes: 0