4

Earlier I asked this question about std::variant. Considering that the types hold by the variant are all printable by std::cout, is there a simple way to implement a visitor?

Here for example, all the way down you have several lambdas to cover each type, but all do the same thing (except std::string): std::cout << arg << ' ';. Is there a way to not repeat my self?

std::visit(overloaded {
            [](int arg) { std::cout << arg; },
            [](long arg) { std::cout << arg; },
            [](double arg) { std::cout << arg; }
            // I removed the std::string case
        }, v); // v is the std::variant

and write instead:

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

or something like:

template<typename T>
void printer(T arg) {std::cout << arg; }
//.......
std::visit(printer, v);
Community
  • 1
  • 1
dani
  • 3,677
  • 4
  • 26
  • 60
  • 2
    The second block (with the generic lambda) should work. The third block (with the template function) won't work because the template isn't a single function or object. You could write your own callable with a template `operator()`: `struct printer { template void operator()(T arg) { std::cout << arg; } }; std::visit(printer{}, v);` (which is roughly equivalent to the generic lambda). – Daniel Schepler May 18 '17 at 22:37
  • 6
    When we keep adding the [tag:c++] tag to your questions, there's a reason. Please do it yourself from now on. – Lightness Races in Orbit May 18 '17 at 23:13

2 Answers2

4

No need to copy

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

this takes arg by (forwarding) reference. I don't bother forwarding it; I don't care if it is an rvalue or lvalue really.

The template function doesn't work, because visit requires an object, and template functions aren't objects of functions; you cannot (yet) pass overload set names as objects in C++.

The overload trick is mainly when you want to dispatch different behavior.

One thing you can do is

template<typename T>
void printer(T arg) {std::cout << arg; }

std::visit([](auto&&arg){printer(arg);}, v);

or

#define RETURNS(...) \
   noexcept(noexcept(__VA_ARGS__)) \
   -> decltype( __VA_ARGS__ ) \
   { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__(decltype(args)(args)...) )

then we get:

template<typename T>
void printer(T arg) {std::cout << arg; }

std::visit(OVERLOADS_OF(printer), v);

which creates an anonymous object that represents the overload set of functions named by the token printer.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

I had the problem with a std::variant having a std::monostate in addition to a a couple of different std::vector<T>s. Instead of overloading I solved it this way:

auto empty() const -> bool
{
    return std::visit([](auto arg) {
        // constexpr() does not work in a ternary operator...
        if constexpr(std::is_same<decltype(arg), std::monostate>()) {
            return true;
        }
        else {
            return arg.empty();
        }},
        v);
}

Not the most elegant, but simple and might get you where you want.

Bim
  • 1,008
  • 1
  • 10
  • 29