Reputation: 3566
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?
Upvotes: 13
Views: 3236
Reputation: 131523
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?
Upvotes: 4
Reputation: 440
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.
Upvotes: 8
Reputation: 39818
This is inherently impossible in C++’s model; consider
template<class T> void f(T);
void g(std::variant<int,double> v) {
auto x=std::get<v.index()>(v);
f(x);
}
Which f
is being called, f<int>
or f<double>
? If it’s “both”, that means that g
contains a branch (which it doesn’t), or that there are two versions of g
(which just pushes the problem onto its caller). And think about f(T,U,V,W)
—where does the compiler stop?
There is actually a proposal for a JIT for C++ that would allow things like this by compiling those additional versions of f
when they’re called, but it’s very early.
Upvotes: 1
Reputation: 3321
The problem arises from templates being instantiated at compile-time while the index you are getting is calculated at run-time. Similarly, C++ types are also defined at compile-time so even with the auto
declaration, res
must have a concrete type for the program to be well-formed. This means that even without the restriction on the template, what you are trying to do is inherently impossible for non-constant expression std::variant
s. How would one work around this?
Firstly, if your variant is indeed a constant expression, the code compiles and works as expected
#include <variant>
int main()
{
constexpr std::variant<int, float, char> var { 42.0f };
constexpr std::size_t idx = var.index();
auto res = std::get<idx>(var);
return 0;
}
Otherwise you will have to use some manual branching mechanism
if (idx == 0) {
// Now 'auto' will have a concrete type which I've explicitly used
int value == std::get<0>(var);
}
You could define these branches using the visitor pattern, see std::visit.
Upvotes: 2
Reputation: 66200
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 };
Upvotes: 3