C++ Logo

std-proposals

Advanced search

Re: Meta types

From: Jake Arkinstall <jake.arkinstall_at_[hidden]>
Date: Fri, 7 Feb 2020 13:51:35 +0000
On Fri, 7 Feb 2020, 12:54 Михаил Найденов, <mihailnajdenov_at_[hidden]> wrote:

>
>
> On Fri, Feb 7, 2020 at 2:21 PM Jake Arkinstall via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Nice.
>>
>> As an example where coroutines don't fit without a suitably sized hammer,
>> consider a type Dictionary<key_t, value_ts...>, holding a set of
>> heterogeneous values (I.e. A pythonic dict) mapped by keys (through e.g. a
>> tuple of tuples), such that the type of element N is static. If you want to
>> access an element based on a key known only at runtime, the standard
>> solutions are to either return a std::variant, or to pass in a callback
>> that can handle every type in the set (value_ts...). Both are fairly ugly,
>> and the former branches twice - once when the initial decision about the
>> type to use is made, and once to figure out what the result type actually
>> was once it has been wrapped up for general use.
>>
>
>
>> A loop over the dictionary would then also apply the same logic. If you
>> pass a callback F, it can call F with each of the types by recursing over
>> the stored values (the decision of which function specialisation to jump
>> into would be known at compilation time). If you return a variant, that
>> decision is passed over to runtime on every single iteration.
>>
>
>
>>
>>
>
>> For the issues with templating of the body, I think this might be doable
>> with a custom block.
>>
>> with(var result = dictionary.get("my key")){
>> // the body to specialise
>> }else{
>> // handle key not found error
>> }
>>
>
> But, can var result be used without `with`?
>

It depends on what people want. I think the "with" (there's probably a
better keyword) syntax is reasonably elegant and allows the user to define
boundaries, which would otherwise default to the whole block,
post-declaration. It'd be one or the other.

If it can, what is the difference with a variant?
>

It cannot be stored as a var, and the type cannot be mutated within a
context. If those are desired, it would be the perfect job for variant
(unlike simply returning one of many types, for which variant is often used
- but for which variant is inefficient).

The two approaches can easily be combined. Whatever type var turns out to
be can be stored in a variant by the call site when state needs to persist,
and that is identical to returning a Variant in the first place.

For example:

-----------
var fizzbuzz(unsigned i){
    if(i % 15 == 0) return FizzBuzz{i};
    if(i % 3 == 0) return Fizz{i};
    if(i % 5 == 0) return Buzz{i};
    return i;
}

std::variant<FizzBuzz, Fizz, Buzz, unsigned> state;
with(var fb = fizzbuzz(x)){
    state = fb;
}
------------

Is identical, in assembly output, to the following, courtesy of inlining:

-----------
std::variant<FizzBuzz, Fizz, Buzz, unsigned> fizzbuzz(unsigned i){
    if(i % 15 == 0) return FizzBuzz{i};
    if(i % 3 == 0) return Fizz{i};
    if(i % 5 == 0) return Buzz{i};
    return i;
}

std::variant<FizzBuzz, Fizz, Buzz, unsigned> state = fizzbuzz(x);
-----------

However, if within the with() block you also used the result for some
processing, that code would be inlined into the returning branch and you'd
have direct access to the FizzBuzz, Fizz, or Buzz, without first needing to
branch on the variant through std::visit.


> The problem is that we want to cross function boundaries and create not
> one but two template instantiations.
>
> - The callback let us limit instantiations to only one function (the
> called function).
>
> - Variant does two template instantiations (the called function and
> result handling function), and has to have some indirection because it has
> to remember the active type. It is also much more rigid in terms of types
>

Yes. Thats another difference with variant. Variant is a way of handling
any type within a set. When storing it, this is necessary. With var, you
can accept /any/ type, or many just any type matching a concept. In that
sense, you get a compile time polymorphism within a function, rather than
on its boundary.


> The plus side is that we can delay the second instantiation and pass the
> value around, sore it etc. We are not forced to imitatively unwrap and
> handle locally.
>

There is some interplay that can go on between variant and the language
variant, but I feel that this does not intrude on any choices made by the
developer. Variant is optimised for storage, var is optimised for direct
access, and the two can interact at the call site (rather than the function
itself returning a variant, implicitly making the choice of storage over
efficiency).

Received on 2020-02-07 07:54:24