Trying to return the value from std::variant using std::visit and a lambda expression
Solution 1
I am trying to get the underlying value from a std::variant using std::visit or std::get.
If what you want is indeed holding the underlying current value, then you must have the visitation support a specific handling of each one possible. For instance, like this:
#include <string>
#include <variant>
int main()
{
using your_variant = std::variant<int,char,double,bool,std::string>;
your_variant v;
std::visit([](your_variant&& arg) {
if (std::holds_alternative<int>(arg))
auto v_int = std::get<int>(arg);
else if (std::holds_alternative<char>(arg))
auto v_chart = std::get<char>(arg);
else if (std::holds_alternative<double>(arg))
auto v_double = std::get<double>(arg);
else if (std::holds_alternative<bool>(arg))
auto v_bool = std::get<bool>(arg);
else if (std::holds_alternative<std::string>(arg))
auto v_str = std::get<std::string>(arg);
}, v);
return 0;
}
This is becasue C++ is a static typed language, as in, all types of variables must be known in compile time. Thus, the compiler cannot allow you to just declare auto
and be done with it when you want what could be one of various types that the std::variant
may hold as the current value at that moment during run-time.
... but I need a way to return the contained value.
Being statically typed, there's no way in C++ to do so without going through the possible cases. If you want a function that takes an instance of such std::variant
and returns, say, a std::string
, then you can modify the above code to return std::to_string()
for each value returned from std::get()
(redundant but just for illustration). Then you'll have the contained type in the "non-std::variant
form".
Taken from the comments:
Then why does std::visit([](auto&& arg){std::cout << arg;}, v); work?
This works because you're not trying to assign/copy the variable of the underlying type into a variable of your own. This, again, would have required knowing the type for such a variable during compilation. But when std::variant
is being required to provide a string representation of its currently held value -- for example due to operator <<
of std::cout
-- then internally what it does is of the same semantics as our if
-else
switch above, i.e. handling differently for each possible underlying type of this variant
instance.
Clarification: There is obviously more than one way to specify handling of the different possibilities of what the std::variant
instance might currently be holding. For example, as shown in the std::visit
cppreference page, you could be using the template deduction guides based std::visit(overloaded { ...
way of doing it, that while arguably makes for better and shorter code, it takes some deeper explaining to understand the mechanics of, the way I see it, as it includes inheriting from a lambda, among other things, and so I figured it to be beyond the explanatory scope of this answer in regards to how I understand the question being asked. You can read all about it here and here. Or easier see the usage code example in another answer to this question.
Regarding the compilation errors: This will compile for you just fine, but it doesn't achieve what you wanted:
using your_variant = std::variant<int,char,double,bool,std::string>;
your_variant v;
auto k = std::visit([](auto arg)-> your_variant {return arg;}, v);
Your lines didn't compile as the lambda needs to declare it's return type by -> your_variant
explicitly as the compiler has no way of inferring it from the lambda.
Another valid syntax to solve the same problem is just declaring the parameter type, so the compiler can know what it's returning as if it was a function returning auto
:
auto k2 = std::visit([](your_variant arg) {return arg;}, v);
The compilation problem with doing this:
constexpr size_t idx = v.index();
auto k = std::get<idx>(v);
is again, due to static typing, that v
could hold any single one of its indices at run-time, and the template argument for std::get()
needs to be known at compile time.
Solution 2
What you are trying to do can't work because the type of the object variant
holds is known at runtime and the type of the variable where you want to store it must be known at compile time.
The pattern to deal with this variant
is to do the work on a template function that can deal with any type, or to have a set of overloads that can accept any type from the variant.
Option 1
Do all work on a template function:
std::visit([] (const auto& k) { std::cout << k; }, v);
Or, inside the function differentiate with constexpr if. But I don't see the point of this one as there is a better alternative imo with overloads (see next):
std::visit([] (const auto& k) {
using T = std::decay_t<decltype(k)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int with value " << k << '\n';
else if constexpr (std::is_same_v<T, char>)
std::cout << "char with value " << k << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double with value " << k << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "std::string with value " << k << '\n';
}, v);
Option 2
Call different overloads
template <class... Fs> struct Overload : Fs... { using Fs::operator()...; };
template <class... Fs> Overload(Fs...) -> Overload<Fs...>;
std::visit(
Overload{
[] (int k) { /* deal with k here */ },
[] (char k) { /* deal with k here */ },
[] (double k) { /* deal with k here */ },
[] (bool k) { /* deal with k here */ },
[] (std::string k) { /* deal with k here */ }
},
v
);
Solution 3
Every variable in a given C++ function has a single, fixed type.
auto k = std::visit([](auto arg){return arg;}, v);
here you want k
to have one of multiple different types. C++ does not support this.
"But", you say, "why does":
std::visit([](auto arg){std::cout << arg;}, v);
work? In the lambda, arg
takes many different types!
That is because [](auto arg){...}
is not a single function, but (shorthand) for a template function. A template function is not a function, but a template for creating functions.
That code causes N different functions to be created, each with a different type for auto arg
. They are all compiled. Then, std::visit
picks one to run.
std::variant
is how we store multiple different possible types of data in one variable. It has a fixed type, but it exposes visit
so you can type-safely get at the underlying data.
Now things aren't that bad. You can just put your code in the lambda.
So instead of:
auto k = std::visit([](auto arg){return arg;}, v);
// code using k
do this:
std::visit([](auto k){
// code using k
}, v);
If you want to return a value, you have to go back into the land of std::variant
. Suppose you want to return std::vector<T>
where T
is the type in the variant.
template<class...Ts>
using var_of_vec = std::variant< std::vector<Ts>... >;
using my_vector = var_of_vec<int,char,double,bool,std::string>;
my_vector v =std::visit([](auto k)->my_vector{
std::vector<decltype(k)> vec;
// code using k
return vec;
}, v);
within the body of the lambda you use a single vector, then you return a variant of vectors.
markf78
I write code for embedded controllers in C/C++/C# that are used in manufacturing.
Updated on June 05, 2022Comments
-
markf78 almost 2 years
Suppose there exists a variant v defined as follows:
std::variant<int,char,double,bool,std::string> v;
I am trying to get the underlying value from a std::variant using std::visit or std::get.
I tried doing this naively:
constexpr size_t idx = v.index(); auto k = std::get<idx>(v);
But then learned that this will fail if the variant v is not a constexpr itself. And even then there may be problems with using std::string (due to the definition of the destructor for std::string).
My 2nd attempt was to try and do the following:
auto k = std::visit([](auto arg){return arg;}, v);
But received this:
$g++ -o main *.cpp --std=c++17 In file included from main.cpp:5:0: /usr/include/c++/7/variant: In instantiation of ‘static constexpr auto std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Tail ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply() [with _Result_type = int; _Visitor = main()::<lambda(auto:1)>&&; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}; long unsigned int ...__indices = {1}]’: /usr/include/c++/7/variant:663:61: required from ‘static constexpr void std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...), __dimensions ...>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply_single_alt(_Tp&) [with long unsigned int __index = 1; _Tp = std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)>; _Result_type = int; _Visitor = main()::<lambda(auto:1)>&&; long unsigned int ...__dimensions = {5}; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}; long unsigned int ...__indices = {}]’ /usr/include/c++/7/variant:651:39: required from ‘constexpr const std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5> std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>::_S_vtable’ /usr/include/c++/7/variant:704:29: required from ‘struct std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>’ /usr/include/c++/7/variant:1239:23: required from ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main()::<lambda(auto:1)>; _Variants = {std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&}]’ main.cpp:89:49: required from here /usr/include/c++/7/variant:704:49: in constexpr expansion of ‘std::__detail::__variant::__gen_vtable<int, main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>::_S_apply()’ /usr/include/c++/7/variant:701:38: in constexpr expansion of ‘std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5>, std::tuple<std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>, std::integer_sequence<long unsigned int> >::_S_apply()’ /usr/include/c++/7/variant:641:19: in constexpr expansion of ‘std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&), 5>, std::tuple<std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&>, std::integer_sequence<long unsigned int> >::_S_apply_all_alts<0, 1, 2, 3, 4>(\xe2\x80\x98result_dec\xe2\x80\x99 not supported by dump_expr#<expression error>, (std::make_index_sequence<5>(), std::make_index_sequence<5>()))’ /usr/include/c++/7/variant:686:43: error: invalid conversion from ‘std::__success_type<char>::type (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&) {aka char (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)}’ to ‘int (*)(main()::<lambda(auto:1)>&&, std::variant<int, char, double, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’ [-fpermissive] { return _Array_type{&__visit_invoke}; } ^
I'm stuck as to why the std::visit call does not work. I thought I supplied a trivial lambda expression which takes all possible types for the variant and returns the underlying value but it appears I am misunderstanding something.
I want to use std::variant now (after initially considering std::any (see avoid writing the same repetitive type-checking code with std::any) but I need a way to return the contained value. Any help would be greatly appreciated. Thank you very much.
-
n. m. over 5 years@markf78 "why does std::visit([](auto&& arg){std::cout << arg;}, v); work?" Because that
auto
in the lambda argument list denotes a template. There is aswitch
or an equivalent insidestd::visit
, and each branch calls a different instantiation of a template. -
Öö Tiib over 5 yearsThe first example is irrelevant to OP since it does not return a value. May be change it to something that converts every option to value of one type for example std::string.
-
Öö Tiib over 5 yearsI meant that if OP wants to get a value using visit from variant then it has to be of certain type like string otherwise it is still variant, union or any and so there are still if else chains on type.
-
markf78 over 5 yearsThe using statement does not compile.
-
Geezer about 2 years@Holger I can however agree the opening illustrative example might unintentionally be passing a bad practice as an advice -- due to the redundant extraction->rewrap misuse you have pointed out. I have fixed it now, hoping you'll agree with the current form. Reason I avoided writing it like this in the first place was the necessary use of
constexpr if
which requires understanding in its own right -- and what was important to me was, again, getting the point across wrt static typing. I now feel like a better choice is not avoiding it but making a note of it like I did in the edit. Thank you