Date: Fri, 7 Feb 2020 17:08:00 +0000
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.
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).
>
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.
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 11:10:49