Search code examples
c++templatesc++17variadic-templatesstd-variant

how to return a specific type from a variant using a visitor?


I have the code below and why visitor1 and visitor2 gives errors? Does that mean the visitor cannot return one type within the variant?

#include <iostream>
#include <variant>


struct Visitor1
{
    template <class T>
    T operator()(const T & t) const
    {
        return (t);
    }
};

struct Visitor2
{
    int operator()(const int  & t) const
    {
        return std::get<int>(t);
    }

    char operator()(const char & t) const
    {
        return std::get<char>(t);
    }
};

struct Visitor3
{
    void operator()(const int & t) const
    {
        std::cout<<t;
    }
    void operator()(const char & t) const
    {
        std::cout<<t;
    }
};

int main()
{
    std::variant<int, char> v{char(100)};

    std::visit(Visitor3{}, v);

    auto t = std::visit(Visitor2{}, v);  //fails
    //auto t = std::visit(Visitor1{}, v); //fails
    std::cout << t;
}

I know I can use std::get(), but the issue is I can only use auto with std::get(), if I do something like below, the x is not accessible outside of the if/else scope:

bool b;
Variant v;
if (b)
{
   auto x = std::get<int>(v);
}
else
{
   auto x = std::get<char>(v);
}
// I want to do something with x here out of if/else

Solution

  • I have the code below and why visitor1 and visitor2 gives errors?

    Because C++ is a strongly typed language.

    When you write

    auto t = std::visit(Visitor2{}, v);  //fails
    

    the compiler must decide compile-time which type is t, so must decide which type return std::visit(Visitor2{}, v).

    If Visitor2 return a char, when v contains a char, or a int, when v contain a int, the compiler can't choose (compile-time!) the type returned from std::visit() [there is also the problem (Visitor2 only) that t, inside operator()'s, is a int or a char, so you can't apply std::get() to it].

    Same problem with Visitor1: the template operator() return the template type so int or char for a std::variant<int, char>.

    Visitor3 works because both operator() return void, so the compiler can resolve (compile-time) that std::visit(Visitor3{}, v) return (in a sense) void.

    Maybe is better explained in this page:

    [std::visit()] Effectively returns

    std::invoke(std::forward<Visitor>(vis), std::get<is>(std::forward<Variants>(vars))...) 
    

    , where is... is vars.index().... The return type is deduced from the returned expression as if by decltype.

    The call is ill-formed if the invocation above is not a valid expression of the same type and value category, for all combinations of alternative types of all variants.