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?
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 if
s-version gets its multiple matches optimized away.
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