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.

Tags:

C++

C++17

Variant