The type of an expression in C++ cannot depend on runtime parameters; basically it can only depend on types of the arguments, plus non-type template arguments.
So d[0]
and d[1]
must have the same type, as the type of the pieces of the expression are identical, and there are no non-type template arguments.
std::get<int>(d[0])
vs std::get<double>(d[1])
can differ in type.
std::get<1>(d[0])
vs std::get<2>(d[1])
can differ in type.
std::visit
is a mechanism used to get around this; here, we create every a function object call, one for each possible type, and then pick one at runtime to actually call. However, the type returned from the visit
still follows the above rule: it doesn't depend on what type is stored in the variant
, and every possible type in the variant
must have a valid instantiation of the function.
C++ type system is not a runtime type system. It is compile-time. Stuff like variant
and dynamic_cast
and any
give some runtime exposure to it, but it is intentionally minimal.
If you are wanting to print the contents of a variant, you can do this:
std::visit([](auto& x){
std::cout << x;
}, d[0]);
the trick here is that each of the various types of variant have a lambda function body written for them (so they all must be valid). Then, at run time, the one actually in the variant is run.
You can also test the variant and ask if it has a specific type, either via std::get
or manually.
bool has_int = std::visit([](auto& x){
return std::is_same_v<int, std::decay_t<decltype(x)>>::value;
}, d[0]);
this gives you a bool saying if d[0]
has an int
in it or not.
The next bit is getting insane. Please don't read this unless you fully understand how to use variants and want to know more:
You can even extract out the type index of the variant and pass that around as a run time value:
template<auto I>
using konstant_t = std::integral_constant<decltype(I),I>;
template<auto I>
constexpr konstant_t<I> konstant_v{};
template<auto...Is>
using venum_t = std::variant< konstant_t<Is>... >;
template<class Is>
struct make_venum_helper;
template<class Is>
using make_venum_helper_t = typename make_venum_helper<Is>::type;
template<std::size_t...Is>
struct make_venum_helper<std::index_sequence<Is...>>{
using type=venum_t<Is...>;
};
template<std::size_t N>
using make_venum_t = typename make_venum_helper<std::make_index_sequence<N>>::type;
template<std::size_t...Is>
constexpr auto venum_v( std::index_sequence<Is...>, std::size_t I ) {
using venum = make_venum_t<sizeof...(Is)>;
constexpr venum arr[]={
venum( konstant_v<Is> )...
};
return arr[I];
}
template<std::size_t N>
constexpr auto venum_v( std::size_t I ) {
return venum_v( std::make_index_sequence<N>{}, I );
}
template<class...Ts>
constexpr auto venum_v( std::variant<Ts...> const& v ) {
return venum_v< sizeof...(Ts) >( v.index() );
}
now you can do this:
using venum = make_venum_t<3>;
venum idx = venum_v(d[0]);
and idx
holds the index of the engaged type in d[0]
. This is only somewhat useful, as you still need std::visit
to use it usefully:
std::visit([&](auto I) {
std::cout << std::get<I>( d[0] );
}, idx );
(within the lambda, I
is a std::integral_constant
, which can be constexpr
converted to an integer.)
but lets you do some interesting things with it.