2

I'm currently writing a method in C++17 where a std::variant is visited through std::visit. The variant is embedded in a object that is passed by reference to the lambda function used in std::visit, and this object is then used in the function to initialize another object, where the type of the variant is needed and obtained with std::get. However, a compile error is raised when I'm trying to do this. I implemented a minimal version of a code reproducing this error, tested with g++ 11.3.0 -std=c++2a:

#include <iostream>
#include <variant>

struct A 
{
    std::variant<int, double> var;
    
    A(int a) : var(a) {};
    
    A(double a) : var(a) {};
};

int main()
{
    auto a = A(1);
    
    const auto & v = a.var;
    
    std::visit([&](const auto & var_impl) {
        auto w = std::get<decltype(var_impl)>(a.var);
        std::cout << w << std::endl;
    }, v);

    return 0;
}

When compiled, I get an error message starting with:

In file included from main.cpp:10:
/usr/include/c++/11/variant: In instantiation of ‘constexpr _Tp& std::get(std::variant<_Types ...>&) [with _Tp = const int&; _Types = {int, double}]’:
main.cpp:28:46:   required from ‘main():: [with auto:34 = int]’
/usr/include/c++/11/type_traits:2536:26:   required by substitution of ‘template<class _Fn, class ... _Args> static std::__result_of_success<decltype (declval<_Fn>()((declval<_Args>)()...)), std::__invoke_other> std::__result_of_other_impl::_S_test(int) [with _Fn = main()::<lambda(const auto:34&)>; _Args = {const int&}]’
/usr/include/c++/11/type_traits:2547:55:   required from ‘struct std::__result_of_impl<false, false, main()::<lambda(const auto:34&)>, const int&>’
/usr/include/c++/11/type_traits:2552:12:   required from ‘struct std::__invoke_result<main()::<lambda(const auto:34&)>, const int&>’
/usr/include/c++/11/type_traits:2997:12:   required from ‘struct std::invoke_result<main()::<lambda(const auto:34&)>, const int&>’
/usr/include/c++/11/type_traits:3009:11:   required by substitution of ‘template<class _Fn, class ... _Args> using invoke_result_t = typename std::invoke_result::type [with _Fn = main()::<lambda(const auto:34&)>; _Args = {const int&}]’
/usr/include/c++/11/variant:1097:11:   required by substitution of ‘template<class _Visitor, class ... _Variants> using __visit_result_t = std::invoke_result_t<_Visitor, std::__detail::__variant::__get_t<0, _Variants, decltype (std::__detail::__variant::__as(declval<_Variants>())), typename std::variant_alternative<0, typename std::remove_reference<decltype (std::__detail::__variant::__as(declval<_Variants>()))>::type>::type>...> [with _Visitor = main()::<lambda(const auto:34&)>; _Variants = {const std::variant<int, double>&}]’
/usr/include/c++/11/variant:1768:5:   required by substitution of ‘template<class _Visitor, class ... _Variants> constexpr std::__detail::__variant::__visit_result_t<_Visitor, _Variants ...> std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main()::<lambda(const auto:34&)>; _Variants = {const std::variant<int, double>&}]’
main.cpp:27:15:   required from here
/usr/include/c++/11/variant:1136:42: error: static assertion failed: T must occur exactly once in alternatives
 1136 |       static_assert(__detail::__variant::__exactly_once<_Tp, _Types...>,
      |                     ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/11/variant:1136:42: note: ‘std::__detail::__variant::__exactly_once’ evaluates to false
/usr/include/c++/11/variant: In instantiation of ‘struct std::variant_alternative<1, std::variant<double> >’:
/usr/include/c++/11/variant:103:12:   required from ‘struct std::variant_alternative<2, std::variant<int, double> >’
/usr/include/c++/11/variant:111:11:   required by substitution of ‘template<long unsigned int _Np, class _Variant> using variant_alternative_t = typename std::variant_alternative::type [with long unsigned int _Np = 2; _Variant = std::variant<int, double>]’
/usr/include/c++/11/variant:1743:5:   required by substitution of ‘template<long unsigned int _Np, class ... _Types> constexpr std::variant_alternative_t<_Np, std::variant<_Types ...> >&& std::get(const std::variant<_Types ...>&&) [with long unsigned int _Np = 2; _Types = {int, double}]’
/usr/include/c++/11/variant:1139:73:   required from ‘constexpr _Tp& std::get(std::variant<_Types ...>&) [with _Tp = const int&; _Types = {int, double}]’
main.cpp:28:46:   required from ‘main():: [with auto:34 = int]’
/usr/include/c++/11/type_traits:2536:26:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/usr/include/c++/11/type_traits:2552:12:   required from ‘struct std::__invoke_result<main()::<lambda(const auto:34&)>, const int&>’
/usr/include/c++/11/type_traits:2997:12:   required from ‘struct std::invoke_result<main()::<lambda(const auto:34&)>, const int&>’
/usr/include/c++/11/type_traits:3009:11:   required by substitution of ‘template<class _Fn, class ... _Args> using invoke_result_t = typename std::invoke_result::type [with _Fn = main()::<lambda(const auto:34&)>; _Args = {const int&}]’
/usr/include/c++/11/variant:1097:11:   required by substitution of ‘template<class _Visitor, class ... _Variants> using __visit_result_t = std::invoke_result_t<_Visitor, std::__detail::__variant::__get_t<0, _Variants, decltype (std::__detail::__variant::__as(declval<_Variants>())), typename std::variant_alternative<0, typename std::remove_reference<decltype (std::__detail::__variant::__as(declval<_Variants>()))>::type>::type>...> [with _Visitor = main()::<lambda(const auto:34&)>; _Variants = {const std::variant<int, double>&}]’
/usr/include/c++/11/variant:1768:5:   required by substitution of ‘template<class _Visitor, class ... _Variants> constexpr std::__detail::__variant::__visit_result_t<_Visitor, _Variants ...> std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = main()::<lambda(const auto:34&)>; _Variants = {const std::variant<int, double>&}]’
main.cpp:27:15:   required from here
/usr/include/c++/11/variant:103:12: error: invalid use of incomplete type ‘struct std::variant_alternative<0, std::variant<> >’
  103 |     struct variant_alternative<_Np, variant<_First, _Rest...>>
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/11/variant:100:12: note: declaration of ‘struct std::variant_alternative<0, std::variant<> >’
  100 |     struct variant_alternative;
      |            ^~~~~~~~~~~~~~~~~~~
/usr/include/c++/11/variant: In instantiation of ‘constexpr _Tp& std::get(std::variant<_Types ...>&) [with _Tp = const int&; _Types = {int, double}]’:

I'm wondering if it is an intended behavior, and if I need then to pass my evaluated variant through all the functions calls in std::visit?

Thank you!

RaymoAisla
  • 161
  • 1
  • 7
  • 2
    `var_impl` has a type `const int&`, that is not contained in the variant. See usage example in the [manual](https://en.cppreference.com/w/cpp/utility/variant/visit). – 273K Mar 28 '23 at 08:01
  • 1
    And why do you try to access get variant by hand? The value of var_impl should contain the same value your get returns. – gerum Mar 28 '23 at 08:02

2 Answers2

4

Maybe the misunderstanding (at least it was mine when I first saw the code) is that the signature is const auto & var_impl and auto is deduced as either double or int and thats the type you want to get. But thats not the type of var_impl, because that is either const int& or const double&.

If that was your line of reasoning then this is the right fix:

std::visit([&] <typename T> (const T& var_impl)  {
    auto w = std::get<T>(a.var);
    std::cout << w << std::endl;
}, v);

Live Demo


PS: Consider the comments you got. The typical use case of visiting a variant is to not use get. I suppose this is either a simplified version of your real code or merely for learning purpose. There is no reason to write the lambda like this when using std::visit, you'd rather just do this:

std::visit([](const auto& w)  {        
    std::cout << w << std::endl;
}, v);
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you, it was effectively the const and reference modifier that forbid the code to compile. Concerning the fact that the practice is to use directly the type, I understand that this should be the good thing to do, but in the unsimplified code, multiples intricated templates functions are called in the visit and the object visited was not in itself a parameter of these functions – RaymoAisla Mar 28 '23 at 08:37
  • @RaymoAisla In the call to visit `var_impl` is the value of the visited object. – 463035818_is_not_an_ai Mar 28 '23 at 09:20
2

The type of decltype(var_impl) is int/double const&, but your variant contains only int/double. You need to apply std::remove_cvref_t<decltype(var_impl)> (documentation) to remove the const& from the argument type.

Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51