When working with std::variant/other similar types huge problem is a compilation time:
 std::visit([](auto&&...){}, x, x, x, x);
https://godbolt.org/z/bWTn18T17 (just an example)

It seems very logical to make just one function that would do nothing by default so that the compiler would not have to instantiate it with all the parameters:
std::visit([](...){}, x, x, x, x);

And this compiles! And even much faster to compile! But there are one problem...
std::visit([](...) {}, std::variant<std::string>{""});
https://godbolt.org/z/73vxa4Yf8

It compiles to undefined behavior and moreover - i even dont understand WHY this compiles on all big 3. Because its ill-formed to pass non-trivial arguments to C-style variadic(string& passed here)(but gcc has extension and its not UB on gcc)

So i propose to allow pass non-trivial types to C-style variadic by passing void* (and materialization + pass address  for prvalues) to them or atleast remove this error-provoking hole.

In fact, i want to propose generalization of std::visit for all summ-types, such as optional, expected, variant, bool (true_type/false_type) and their inheritors and even just any other type (its basically summ-type where it is always only one variant: T)

We can do it as structured binding for tuple-like types.
specializing variant_alternative<Index, Variant>, variant_size<Variant> and get<Index>(Variant&&), also i think we need create customization for accessing to current .index(for example adl function variant_index(X&&)

This will allow to use your type in std::visit and other (possibly in future) pattern matchng expressions like structured binding for tuples now.

Obviously best candidate for such update is 'switch'
First problem existing now is no way to use variadic arguments with 'case'.
This creates very bad compiation times and very bad performance on existing implementations of std::visit:
https://godbolt.org/z/KsTnzr1a5 (clang uses function pointers table and even not optimizing it away in primitive case)

We can allow 'switch' on non integral types to work as pattern matching, but i dont think we need it when we have std::visit, we must improve it.

1. allow expand 'case' with variadic pack
2. add std::macher.
template<typename... Foos>
constexpr /*unspecified*/ matcher(Foos&&... foos) noexcept(...);
template<typename Ret, typename... Foos>
constexpr /*unspecified*/ matcher_r(Foos&&... foos) noexcept(...);

This function creates object of unspecified type, which operator() performs INVOKE by overload resolution of 'Foos' (second version makes INVOKE_R with cast to result type)

And helper types:

// tag types used for std::matcher
constexpr inline /*unspecified*/ unreachable_fn; // hint for implementation, means this function never called
struct noop_t {};
constexpr inline /*unspecified*/ noop_fn; // hint for implementation  means does nothing (may be returned from foo)

// std::matcher undestands, that this function must be called in other overload resolution fails for other functions and has operator = for noop_t, unreachable_t and other foos
constexpr inline /*unspecified*/ default_fn = {};

// decltype(default_fn) has overloaded operator= such as
expression default = unreachable_fn returns unreachable function
expression default = noop_fn returns noop function
expression default = foo returns just 'foo' like std::identity

This will allow implementation make huge optimizations of compile speed(such as noop fn[](...){} for gcc) and will express intensions better

Example of code in possibly C++26:
    auto matcher = std::matcher_r<int>(
        [](int, int) { /*...*/ },
        [](std::invocable<float> auto, float) { /*...*/ },
// noop fn must be never invoked, no return
        [](ConceptName auto) -> std::noop_t {},
        std::default_fn = std::unreachable_fn
    );
    std::visit(matcher, var1, var2);

And last problem: this MUST compile
int main() {
    std::variant<int, int> var;
    std::get_if<int>(&var);
}