Search code examples
c++c++17variant

The advantage of std::visit over if-else


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?


Solution

  • 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