Why can I not retrieve the index of a variant and use that to get its content?
Essentially, you cannot.
You wrote:
I don't know what's in there, but thankfully, the variant does
... but only at run-time, not at compile-time.
And that means your idx
value is not compile-time.
And that means you can't use get<idx>()
directly.
Something you could do is have a switch statement; ugly, but it would work:
switch(idx) {
case 0: { /* code which knows at compile time that idx is 0 */ } break;
case 1: { /* code which knows at compile time that idx is 1 */ } break;
// etc. etc.
}
This is rather ugly however. As comments suggest, you might as well std::visit()
(which is not very different from the code above, except using variadic template arguments instead of being this explicit) and avoid the switch altogether. For other index-based approaches (not specific to std::variant
), see:
Idiom for simulating run-time numeric template parameters?
The problem is that std::get<idx>(var);
require (for idx
) a compile time known value.
So a constexpr
value
// VVVVVVVVV
constexpr std::size_t idx = var.index();
But to initialize idx
as constexpr
, also var
had to be constexpr
// VVVVVVVVV
constexpr std::variant<int, float, char> var { 42.0F };
The compiler needs to know the value of idx
at compilation time for std::get<idx>()
to work, because it is being used as a template argument.
First option: If the code is meant to run at compile time, then make everything constexpr
:
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
constexpr auto res = std::get<idx>(var);
This works because std::variant
is constexpr
friendly (its constructors and methods are all constexpr
).
Second option: If the code is not meant to run at compile time, which is likely the case, the compiler cannot deduce at compile time the type of res
, because it could be three different things (int
, float
or char
). C++ is a statically-typed language, and the compiler must be able to deduce the type of auto res = ...
from the expression that follows (i.e. it must always be the same type).
You can use std::get<T>
, with the type instead of an index, if you already know what it will be:
std::variant<int, float, char> var { 42.0f }; // chooses float
auto res = std::get<float>(var);
In general, use std::holds_alternative
to check if the variant is holding each of the given types, and handle them separately:
std::variant<int, float, char> var { 42.0f };
if (std::holds_alternative<int>(var)) {
auto int_res = std::get<int>(var); // int&
// ...
} else if (std::holds_alternative<float>(var)) {
auto float_res = std::get<float>(var); // float&
// ...
} else {
auto char_res = std::get<char>(var); // char&
// ...
}
Alternatively you can use std::visit
. This is slightly more complicated: you can use a lambda/templated function that is type-agnostic and works for all of the variant's types, or pass a functor with an overloaded call operator:
std::variant<int, float, char> var { 42.0f };
std::size_t idx = var.index();
std::visit([](auto&& val) {
// use val, which may be int&, float& or char&
}, var);
See std::visit for details and examples.