C++ Logo

std-proposals

Advanced search

[std-proposals] DR - std::visit non-intuitive behavior + new semantic for C-style variadic functions + pattern matching but better

From: Nikl Kelbon <kelbonage_at_[hidden]>
Date: Sat, 3 Jun 2023 15:09:17 +0400
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);
}

Received on 2023-06-03 11:09:33