7

I want to get the active value in a std::variant without knowing which one is active. I thought i could write a template visitor and use std::visit but it doesn't work.

#include <variant>
#include <string>
#include <iostream>

struct Visit_configuration {
    template<typename Data_type>
    Data_type operator()(Data_type& t) const
    {
        return t;
    }
};

int main()
{
    std::variant<int, std::string> v;
    v = "hello";

    std::cout << std::visit(Visit_configuration(), v);   // expect "hello"
    std::cin.get();
}

MSVC doesn't compile and throws:

error C2338: visit() requires the result of all potential invocations to have the same type and value category (N4741 23.7.7 [variant.visit]/2).

note: see reference to function template instantiation 'int std::visit&,0>(_Callable &&,std::variant &)' being compiled

So how to fix this?

edit: I want to use the obtained value maybe also for other so putting cout in the template isn't what im looking for.

Sandro4912
  • 313
  • 1
  • 9
  • 29
  • While you and I can see that `v` will always contain a `std::string`, and a good static analysis tool will could also make that determination, it's beyond what a compiler is required to be able to deduce. Notably, `v` isn't `constexpr` and the compiler doesn't understand what `v` contains at compile time any more than if you depended on user input. So the return type of `std::visit` can't be determined. – François Andrieux Feb 14 '19 at 16:36
  • You should have the visitor `operator()` output the value, rather than trying to return it from the visitor so the caller can output it. For [example](https://en.cppreference.com/w/cpp/utility/variant/visit): `std::visit([](auto&& arg){std::cout << arg;}, v);` – Tony Delroy Feb 14 '19 at 16:40

2 Answers2

12

Ask yourself the question:
What is the return type of std::visit if you don't know what part of the variant is active?

That is the question that the compiler must answer. And the answer can't be "it depends" - you (as in, the compiler) must decide on exactly one type at compile-time. The visit call cannot possibly return different types at runtime.

If you want to work with different types "at runtime", you must be in a function templated on the type you want to work with. In other words, there must be different functions (or function template instantiations) to handle the "write an int to cout" and "write a string to cout" cases. You cannot do this in the same (non-templated) function.

The straightforward solution here is thus to put the std::cout << into your templated visitor function - that's the point of visiting: Specifying what is supposed to happen in each case.

If you want to "use the obtained value maybe also for [some] other [purpose]", then that "other purpose" should also be part of the/a visitor. Only then can you have that "other purpose" handle the different cases at once (e.g. in a templated function). Otherwise you must decide at compile-time already which type shall be used - the compiler is not going to leave that choice open for later (run time).

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
4

Return type of visitor function should be identical.

Create printer visitor instead:

struct PrinterVisitor {
    template<typename T>
    void operator()(const T& t) const
    {
        std::cout << t;
    }
};

int main()
{
    std::variant<int, std::string> v;
    v = "hello";

    std::visit(PrinterVisitor{}, v);   // expect "hello"
}

And in your case, you can even have lambda:

int main()
{
    std::variant<int, std::string> v;
    v = "hello";

    std::visit([](const auto& t){std::cout << t;}, v);   // expect "hello"
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302