Convert Std::Variant to Another Std::Variant with Super-Set of Types

Convert std::variant to another std::variant with super-set of types

This is an implementation of Yakk's second option:

template <class... Args>
struct variant_cast_proxy
{
std::variant<Args...> v;

template <class... ToArgs>
operator std::variant<ToArgs...>() const
{
return std::visit([](auto&& arg) -> std::variant<ToArgs...> { return arg ; },
v);
}
};

template <class... Args>
auto variant_cast(const std::variant<Args...>& v) -> variant_cast_proxy<Args...>
{
return {v};
}

You might want to fine tune it for forwarding semantics.

And as you can see its use is simple:

std::variant<int, char> v1 = 24;
std::variant<int, char, bool> v2;

v2 = variant_cast(v1);

Casting a variant to super set variant or a subset variant

You can add a static_assert checking if any of the possibly-held variants are convertible:

static_assert((std::is_convertible_v<Args, std::variant<ToArgs...>> || ...),
"No possible variant that could be converted exists");

Or if you want SFINAE, you can do it in the template arguments:

    // extracted into helper function
template <class... ToArgs>
static constexpr bool is_convertible() noexcept {
return (std::is_convertible_v<Args, std::variant<ToArgs...>> || ...);
}

template<class... ToArgs, std::enable_if_t<is_convertible<ToArgs...>(), int> = 0>
operator std::variant<ToArgs...>() const
{
// ...
}

Convert `std::any` to `std::variant`

This code takes a std::any object along with a list of types and converts the object to std::variant or throws std::bad_any_cast if the stored type is not one of the given types.

#include <any>
#include <variant>
#include <optional>
#include <typeinfo>

template <class... Args>
auto any_to_variant_cast(std::any a) -> std::variant<Args...>
{
if (!a.has_value())
throw std::bad_any_cast();

std::optional<std::variant<Args...>> v = std::nullopt;

bool found = ((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...);

if (!found)
throw std::bad_any_cast{};

return std::move(*v);
}

Example usage:

auto test(const std::any& a)
{
auto v = any_to_variant_cast<int, std::string>(a);

std::visit([](auto val) { std::cout << val << std::endl; }, v);
}

Code on godbolt


Some explanations:

std::optional<std::variant<Args...>> is used because std::variant<Args...> default constructor constructs the variant holding the value-initialized value of the first alternative and requires the first alternative to be default constructible.

   ((a.type() == typeid(Args) && (v = std::any_cast<Args>(std::move(a)), true)) || ...)
// ------------------------ -------------------------------------
// type_check any_cast

This is a fold expression. I've renamed some of the subexpression to be easier to explain. With the renaming the expression becomes:

// ((type_check && (any_cast, true)) || ...)
  • if type_check is false then:

    • (any_cast, true) is not evaluated due to the short circuit of &&
    • (type_check && (any_cast, true)) evaluates to false
    • the next op in the fold expression is evaluated
  • if type_check is true then:

    • (any_cast, true) is evaluated:

      • any_cast is evaluated. The variant get the value from the any object.
      • any_cast result is discarded
      • true is evaluated
      • (any_cast, true) evaluates to true
    • (type_check && (any_cast, true)) evaluates to true
    • the rest of the fold is not evaluated due to the short circuit of ||
    • the whole expression (the fold) evaluates to true
  • if no type_check evaluates to true then the whole expression (the fold) evaluates to false

dealing with variants containing move-only types

template<template<class...>class, class> struct is_instance_of:std::false_type{};
template<template<class...>class Z, class...Ts> struct is_instance_of<Z,Z<Ts...>>:std::true_type{};

template<template<class...>class Z, class T>
constexpr bool is_instance_of_v=is_instance_of<Z,T>::value;

flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
if constexpr (is_instance_of_v<std::unique_ptr, T>){
return *e;
else
return e;
}, v);
}

we add a trait to dispatch on, then use if constexpr to have 2 function bodies.

In c++20 we have lots more options.

[]<class T>(T const& e)->flattened_variant{
if constexpr (is_instance_of_v<std::unique_ptr, T>){

Then, going back to overloading solution, we have:

[]<class T>(std::unique_ptr<T> const&)

or

template<class T, template<class...>class Z>
concept instance_of=is_instance_of<Z,T>::value;

then

[](instance_of<std::unique_ptr> auto const& e)

or

[]<<instance_of<std::unique_ptr> T>(T const& e)

Prior to c++17 in c++14 we can use a dispatch helper:

template<class T0, class T1>
constexpr T0&& constexpr_branch( std::true_type, T0&& t0, T1&& ) { return std::forward<T0>(t0); }
template<class T0, class T1>
constexpr T1&& constexpr_branch( std::false_type, T0&&, T1&& t1 ) { return std::forward<T1>(t1); }

flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
return constexpr_branch(
is_instance_of<std::unique_ptr, T>,
[](auto const& e){return *e;},
[](auto const& e){return e;}
)(e);
}, v);
}

going back to c++11 (where did you get your variant?), you could make an external class:

template<class R>
struct flatten {
template<class T>
R operator()(std::unique_ptr<T> const& p)const{
return *p;
}
template<class T>
R operator()(T const& v)const{
return v;
}
};

then just do a

return std::visit( flatten<flattened_variant>{}, v );

Conversion of variant, vectorvariant and vectorvectorvariant to the equivalent type of my choice

I would suggest the following to get the types you want:

template<typename T>
class converter_visitor : public boost::static_visitor<>
{
public:
std::vector<T>& vec;

converter_visitor(std::vector<T>& r) : vec(r) {}

// only push back values of specific types...
void operator()(const T& u) const {
vec.push_back(u);
}

// ignore other types...
void operator()(...) const {}
};

template<typename T>
converter_visitor<T> make_visitor(std::vector<T>& r) { return converter_visitor<T>(r); }

and then funnel that to a recursive filter func that can handle nested vectors:

template<typename T,typename U>
void filter(std::vector<T>& result,const U& var) {
boost::apply_visitor( make_visitor(result), var );
}

template<typename T,typename U>
void filter(std::vector<T>& result,const std::vector<U>& cont) {
std::for_each(cont.begin(),cont.end(),[&](const U& c) {
filter(result,c);
});
}

then you can do:

_var v = 314;
std::vector<int> result;
filter(result,v);
print(result);

result: 314

_vec_var v;
v.push_back(2);
v.push_back(3);
v.push_back("hello");
v.push_back(5);
v.push_back(7);
v.push_back("world");

std::vector<int> result;
filter(result,v);
print(result);

std::vector<std::string> result2;
filter(result2,v);
print(result2);

result1: 2 3 5 7

result2: hello world

_vec_var v1;
v1.push_back(11);
v1.push_back(13);
v1.push_back("see ya");

_vec_var v2;
v2.push_back(17);
v2.push_back(19);
v2.push_back("later");

_vec2_var vv;
vv.push_back(v1);
vv.push_back(v2);

std::vector<int> result;
filter(result,vv);
print(result);

std::vector<std::string> result2;
filter(result2,vv);
print(result2);

result1: 11 13 17 19

result2: see ya later

see live demo here

A simple way to convert to/from VARIANT types in C++

Well, most of the hard work is already done for you with the various wrapper classes. I prefer _variant_t and _bstr_t as they are more suited for conversion to/from POD types and strings. For simple arrays, all you really need is template conversion function. Something like the following:

// parameter validation and error checking omitted for clarity
template<typename T>
void FromVariant(VARIANT Var, std::vector<T>& Vec)
{
CComSafeArray<T> SafeArray;
SafeArray.Attach(Var.parray);
ULONG Count = SafeArray.GetCount();
Vec.resize(Count);
for(ULONG Index = 0; Index < Count; Index++)
{
Vec[Index] = SafeArray[Index];
}
}
....
std::vector<double> Vec;
VARIANT Var = ...;
FromVariant(Var, Vec);
...

Of course things get hairy (in regards to memory / lifetime management) if the array contains non-POD types, but it is still doable.

I'm working on heterogenous list that uses std::variant. Is there a way to initialize it every time with a custom set of types for std::variant?

  1. Is this just a bad idea of using std::variant in this case?

There is too little context, so I have to assume that you are using std::variant because std::variant is what you need.


  1. If yes, should I use templates or std::any?

std::any is for something very different. std::variant is for a limited set of types, std::any is for any type. Pick one or the other depending on what you actully need, not based on easier refactoring or the like.


  1. Is there a way to store types and pass them into HVector during initialization?

You can use an alias:

using my_variant = std::variant<Party,Loot>;

If you like you can make the classes templates and provide the variant type as parameter:

template <typename T>
class HVector:
{
public:
HVector();
void push(T newData);
void show();
};

this will be more flexible but isn't really necessary.


  1. Are refactoring tools a solution to the problem?

Once you have the alias my_variant and you use that consistently throughout your code, you only have to change it in one single place.

PS: Publicly inheriting from standard containers is often not a good idea. It can be done right, but you have to be careful. I allowed myself to simply ignore that part in above example, because it is only remotely related to the question.

PPS: The situation with std::variant is a bit similar to std::pair. What I mean is that genericity comes at the price of not meaningful names. Even if you use that variant in only one single place in your code, you should give it a meaningful name. When reading your code, I don't understand what a std::variant<Party,Loot> really is. Would be different with an alias that tells me something about the meaning. I didn't know what is a good name. my_variant certainly isn't one.



Related Topics



Leave a reply



Submit