C++ Logo

std-proposals

Advanced search

Re: Meta types

From: Михаил Найденов <mihailnajdenov_at_[hidden]>
Date: Fri, 7 Feb 2020 20:12:08 +0200
On Fri, Feb 7, 2020 at 7:08 PM Lee Howes <xrikcus_at_[hidden]> wrote:

> Well the idea would be that you could unroll it too:
> for co_await(auto [key, value] : dictionary) {
> foo(key, value);
> }
> (I forget what we intended for for co_await syntax, but it's not in there
> yet anyway so let's go with it)
>
> The code would end up instantiating foo over all the key/value type
> combinations. The coroutine callback logic for dictionary iteration would
> yield each key/value pair by callback directly without any variant involved.
>
> This is all speculation though, because actually implementing this and
> convincing people that the reader will not be confused is still a bit of a
> step.
>

This is template for, I believe.


>
> Lee
>
> On Fri, 7 Feb 2020 at 13:51, Jake Arkinstall <jake.arkinstall_at_[hidden]>
> wrote:
>
>>
>>
>> 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 12:14:59