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

3.8k Views Asked by At

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
3

There are 3 best solutions below

0
On BEST ANSWER

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.

0
On

A language could exist with many features of C++ that does what you want.

In order to do what you want, when you call std::visit, N different implementations of the rest of the function would have to be written.

In each of those N different implementations (2 in your case), the type of a variable would be different.

C++ doesn't work that way.

The only part of code that is "multiplied" by the visit call is the visitor.

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

  std::visit([&](auto && t){
    std::cout << t;
  }, v);
}

I put the rest of the body of the function within the visitor. That code is instantiated once for every type that can be stored within the visitor.

Anything that returns from the visit goes back to the "single instance" body of the calling scope.

Basically, [&](auto&& t) lambdas do what you seem to want.


Now, we can do some tricks to change the syntax a bit.

My favorite is:

v->*visit*[&](auto&& val) {
  std::cout << val;
  return [val](auto&& x) { x << val; };
}->*visit*[&](auto&& outputter) {
  outputer(std::cout);
};

where ->*visit* uses a relatively ridiculous amount of metaprogramming to allow

  1. Named operators to cause visiting,

  2. Fusing the return values of the visits into a variant.

but no sane person would write that code.

3
On

You can do

bool b;
Variant v;
std_optional<char> x_char;
std_optional<int> x_int;
if (b)
{
   x_int = std::get<int>(v);
}
else
{
   x_char = std::get<char>(v);
}