0

I encountered an issue where I need to use std::visit() on two std::variant objects which are guaranteed to host the same type at the time of the call. The following code compiles without issues:

std::variant<int, float> var1 = get_variant();
std::variant<int, float> var2 = get_variant();

std::visit([](auto val1, decltype(val1) val2) {
    // ...
var1, var2);

However, I want to do the same with references and it seems that I can't. The following code does not compile:

std::visit([](auto&& rref1, decltype(rref1) rref2) {
    // ...
}, var1, var2);

I am not using auto for both arguments to force the compiler to only generate two instances of the lambda with argument types: (int, int), (double, double). In my actual program, the variants have 10 types and I would prefer that only 10 functions are generated instead of all possible combinations which is 10^2 = 100.

I am surprised that the second snippet does not compile, can anyone tell me why?

Edit - Compiler Output (gcc 9.4)

These are the first few lines of errors:

/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant: In instantiation of ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke_impl(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’:
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:989:28:   required from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__do_visit_invoke(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1005:28:   required from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1014:28:   required from ‘static constexpr auto std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type (*)(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply() [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:909:48:   recursively required from ‘static constexpr void std::__detail::__variant::__gen_vtable_impl<__same_return_types, 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&, _Tp*) [with bool __do_cookie = false; long unsigned int __index = 0; _Tp = std::__detail::__variant::_Multi_array<void (*)(main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&, std::variant<int, float>&, std::variant<int, float>&), 2>; bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; long unsigned int ...__dimensions = {2, 2}; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:909:48:   required from ‘constexpr const _Array_type std::__detail::__variant::__gen_vtable<true, void, main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&, std::variant<int, float>&, std::variant<int, float>&>::_S_vtable’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1647:23:   required from ‘constexpr decltype(auto) std::__do_visit(_Visitor&&, _Variants&& ...) [with bool __use_index = false; bool __same_return_types = true; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>; _Variants = {std::variant<int, float>&, std::variant<int, float>&}]’
Phiv
  • 1
  • 1
  • for questions about code that does not compile please include the compiler error message in the question – 463035818_is_not_an_ai May 02 '23 at 13:40
  • and please provide a [mcve] – 463035818_is_not_an_ai May 02 '23 at 13:41
  • 5
    Even though both variants "are guaranteed to host the same type at the time of the call", that's not good enough. C++ requires that all possible combination of variants must be well-formed, for the visitor. The only reason the non-reference code compiles is because the generated visitor for non-similar types has one of the parameters converted. ints can be converted to floats, and vice versa. The same is not true for references. – Sam Varshavchik May 02 '23 at 13:45
  • 1
    P.S. Yes, the compiler will generate all 100 functions. Guaranteed. The most you can hope for is that many of those functions will be empty, or are duplicates. And, maybe you'll get extremely lucky and the compiler will notice a lot of dead code, and end up eliminating it. But all 100 functions must still compile, and be well-formed. This is fundamental to C++. – Sam Varshavchik May 02 '23 at 13:48
  • Makes sense, thanks! I hoped I could tell it only to generate the specified functions and throw std::bad_variant_access for other cases to save executable space (the lambda in there might be actually a bit big). I'm afraid the compiler cannot optimize away any of the 100 functions since it cannot know the variant hosted types at compilation time. – Phiv May 02 '23 at 14:16
  • @Phiv : This is what `std::unreachable()` is for (but enabling LTO couldn't hurt) – ildjarn May 02 '23 at 15:10
  • @Phiv You can't do that out-of-the-box, but here is a function that does that: (`std::visit` the first variant, then `std::get` from the rest) – Artyer May 02 '23 at 17:28

0 Answers0