6

I have figured out that std::visit can be used the following way:

    std::visit([](auto&& arg) {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, int>)
            std::cout << "int with value " << arg << '\n';
        else if constexpr (std::is_same_v<T, std::string>)
            std::cout << "std::string with value " << std::quoted(arg) << '\n';
        else 
            static_assert(always_false_v<T>, "non-exhaustive visitor!");
    }, v);

But instead, I figured I could also just use

    if(auto x = std::get_if<int>(&v))
        std::cout << " is int " << *x << std::endl;
    else if(auto x = std::get_if<std::string>(&v))
        std::cout << " is String " << *x << std::endl;
    else
        std::cout << "non-exhaustive visitor!" << std::endl;

The only disadvantage that I see right now is that I do not have a static message for when my matching is not exhaustive. Is there any other advantage of using std::visit that I am not seeing?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
DottyPhone
  • 320
  • 1
  • 12
  • 1
    "*Is there any other advantage of using std::visit that I am not seeing?*" `std::visit` can also handle *multiple* variants, and *can* visit classes derived from `std::variant`. – 康桓瑋 Dec 11 '21 at 12:35

1 Answers1

12

Is there any other advantage of using std::visit that I am not seeing?

Yes. With std::visit you can use the built-in function overload resolution instead of matching against all of the types manually:

template<typename... Fs> struct Overload: Fs... { using Fs::operator()...; };
template<typename... Fs> Overload(Fs...) -> Overload<Fs...>;

static_assert(visit(Overload{
    [](int) { return "int"; },
    [](std::string_view) { return "string_view"; },
    [](auto) { return "something else"; }
}, std::variant<int, std::string_view, bool, double>{42}) == "int"sv);

Also, visit might compile to faster code because of only one type match, but it should be checked whether the ifs-version gets its multiple matches optimized away.

Update

As @Quentin mentioned in a comment,

unlike the manual if ladders, an overload will be selected not if it is an exact match, but merely if it is callable (via conversions if needed)

If there is an overload where conversions are undesirable, this technique should help:

[](std::same_as<int> auto) {} // C++20

or

template<typename T, typename U> using SameAs = std::enable_if_t<std::is_same_v<T, U>>;

[](auto t, SameAs<decltype(t), int>* = 0) {} // C++17
passing_through
  • 1,778
  • 12
  • 24
  • 1
    In case it isn't clear, removing the `auto` overload will catch missing types as requested. However it's worth noting that, unlike the manual `if` ladders, an overload will be selected not if it is an exact match, but merely if it is callable (via conversions if needed). IOW, visiting an `std::variant` with only an `int` overload will compile and truncate a `float`. – Quentin Dec 11 '21 at 12:30
  • In general the jump table *should* be generated at compile time which makes the code a bit faster. BUT I run into trouble with very slow code by having this one: https://stackoverflow.com/questions/49146629/optimizing-of-stdvisit-possible – Klaus Dec 11 '21 at 12:34
  • I've updated the answer in case the effect described by @Quentin was a problem. Also added a missing deduction guide. – passing_through Jan 09 '22 at 15:16