Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I not retrieve the index of a variant and use that to get its content?

Tags:

c++

c++17

variant

I'm trying to access the content of a variant. I don't know what's in there, but thankfully, the variant does. So I thought I'll just ask the variant what index it is on and then use that index to std::get its content.

But this does not compile:

#include <variant>

int main()
{
  std::variant<int, float, char> var { 42.0F };

  const std::size_t idx = var.index();

  auto res = std::get<idx>(var);

  return 0;
}

The error happens in the std::get call:

error: no matching function for call to ‘get<idx>(std::variant<int, float, char>&)’
   auto res = std::get<idx>(var);
                               ^
In file included from /usr/include/c++/8/variant:37,
                 from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: ‘template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
     get(std::pair<_Tp1, _Tp2>& __in) noexcept
     ^~~
/usr/include/c++/8/utility:216:5: note:   template argument deduction/substitution failed:
main.cpp:9:31: error: the value of ‘idx’ is not usable in a constant expression
   auto res = std::get<idx>(var);
                               ^
main.cpp:7:15: note: ‘std::size_t idx’ is not const
   std::size_t idx = var.index();
               ^~~

How can I fix this?

like image 877
Alex Avatar asked Nov 29 '19 10:11

Alex


People also ask

What does std :: variant do?

The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).

Does STD variant use RTTI?

Since this function is specific to a given type, you don't need RTTI to perform the operations required by std::any .

What is boost :: variant?

Boost. Variant, part of collection of the Boost C++ Libraries. It is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner.


2 Answers

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.

like image 123
elbrunovsky Avatar answered Oct 24 '22 11:10

elbrunovsky


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?

like image 39
einpoklum Avatar answered Oct 24 '22 09:10

einpoklum