Search code examples
c++c++17std-variant

std::variant behavior leads to an error when calling std::get inside std::visit: Is it intended?


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!


Solution

  • 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);