C++ Logo

std-proposals

Advanced search

Meta types

From: Jake Arkinstall <jake.arkinstall_at_[hidden]>
Date: Tue, 4 Feb 2020 11:27:06 +0000
Hi all,

I want to put this out there and see if there is any interest or not for a
far future C++ version. It's along the lines of something I use for data
pipelines with components that return one of many possible types (or yield
multiple of them).

I want the compiler to be able to effectively optimise several return types
at compile time, without resorting to the Variant's use of double-branching
at runtime (branch on a condition, create some T, wrap it in a variant,
store it in X, unwrap X, branch on T). I also want it to feel more natural
to use, almost as effortless as python, but with the power of C++'s type
engine.

The use of callbacks allows the compiler to reason about different branches
rather effectively - in both clang and gcc, with full optimisation enabled,
this produces far smaller binaries, faster runtimes and (on the vanity
side) much nicer assembly.

Here's the idea:

Through templates, a single template function can generate many functions,
each determined by the signatures of one or more calls that exist in the
code being compiled. It is possible to use this mechanism to flip that idea
on its head, allow multiple return types T, and generate functions at the
call site to handle each T.

Consider a placeholder type, which I'll call var, that acts as "one of many
types, to be determined at compile time". This keyword can be applied as a
return type to a function:

var halve(unsigned i){
    if(i % 2 == 0){
        return i / 2; // unsigned path
    }else{
        return i * 0.5; // double path
    }
}

And it can be used as a holder for that return type:

void main(int argc, char const* const* argv){
    var x = halve(args);
    std::cout << "You passed 2*(" << x << ") arguments\n";
    return 0;
}

This can be achieved by injecting callbacks which have x defined as the
type being passed during invocation. In this case, the var function could
be rewritten, implicitly, as:

template<typename callback_t>
void halve(callback_t&& callback, unsigned i){
    if(i % 2 == 0){
        std::invoke(callback, i / 2);
        return;
    }else{
        std::invoke(callback, i * 0.5);
        return;
    }
}

And the function with the var placeholder could be rewritten as:

struct main_x_handler{
    void operator()(unsigned x){
        std::cout << "You passed 2*(" << x << ") arguments\n";
        return 0;
    }
    void operator()(double x){
        std::cout << "You passed 2*(" << x << ") arguments\n";
        return 0;
    }
};
void main(int argc, char const* const* argv){
    return halve(main_x_handler{}, argc);
}

such that all code that follows the first read of x is diverted into a
callback that caters for each possible type that x can adopt.

Furthermore, we can add a "multivar" keyword that allows yielding, rather
than returning after the first invocation of the callback. Placed in a
loop, this invokes the remainder of the calling function's body for each
such yield.

multivar get_halves(unsigned start, unsigned end){
    for(unsigned i = start; i < end; ++i){
         yield halve(i);
    }
}
void foo(unsigned start, unsigned end){
    for(var x : get_halves(start, end)){
        // use x
    }
}

This is particularly useful for me, at least, because I use cleaning
methods that may hold back events until further information is received, at
which point it flushes many events in sequence.

This proposal will likely also include a mechanism for switching on types.
I'm also thinking of using template/concept syntax in place of var.

The idea is just a rough sketch at the moment, and I don't want to take it
too far without some feedback.

Thanks,
Jake

Received on 2020-02-04 05:29:53