C++ Logo

sg7

Advanced search

Re: Update: P1240R2 — Scalable Reflection

From: Daveed Vandevoorde <daveed_at_[hidden]>
Date: Thu, 20 Jan 2022 10:28:23 -0500
> On Jan 20, 2022, at 8:05 AM, David Rector <davrec_at_[hidden]> wrote:
>
>>
>> On Jan 19, 2022, at 4:25 PM, Daveed Vandevoorde <daveed_at_[hidden] <mailto:daveed_at_[hidden]>> wrote:
>>
>>
>>
>>> On Jan 19, 2022, at 9:24 AM, David Rector <davrec_at_[hidden] <mailto:davrec_at_[hidden]>> wrote:
>>>
>>>
>>>
>>>> On Jan 18, 2022, at 11:45 AM, Daveed Vandevoorde via SG7 <sg7_at_[hidden] <mailto:sg7_at_[hidden]>> wrote:
>>>>
>>>>
>>>>
>>>>> On Jan 18, 2022, at 5:24 AM, Andrey Davydov <andrey.davydov_at_[hidden] <mailto:andrey.davydov_at_[hidden]>> wrote:
>>>>>
>>>>> On Tue, Jan 18, 2022 at 1:09 AM Daveed Vandevoorde <daveed_at_[hidden] <mailto:daveed_at_[hidden]>> wrote:
>>>>>> On Jan 16, 2022, at 8:36 AM, Andrey Davydov <andrey.davydov_at_[hidden] <mailto:andrey.davydov_at_[hidden]>> wrote:
>>>>>>
>>>>>> I wonder what is the lifetime of underlying storage referenced by std::span returned from such functions as std::meta::members_of? Is this array persisted throughout the compilation process?
>>>>> That’s a good question (and one of the reasons I was reluctant to make the vector->span change at first).
>>>>>
>>>>> An implementation is of course free to recompute the array if it needs to. However, if the array “leaks” outside constant-evaluation, it is likely to be made persistent, but doesn’t leak.
>>>>>
>>>>> I don’t expect span<info> objects to typically “leak”, but the “string_view” objects yes.
>>>>>
>>>>> Thanks for the response!
>>>>>
>>>>> I agree that `string_view` could easily "leak", a function `std::string_view to_string(Enum auto e);` is one of such examples,
>>>>
>>>> Exactly.
>>>>
>>>>> but I don't see how to use `span<info>` outside of constant-evaluation, because `meta::info` couldn't be used in runtime and `span<info>` is not a structural type and so couldn't be used as NTTP.
>>>>
>>>> Someone might be tempted to write:
>>>>
>>>> constexpr auto mems = members_of(^S);
>>>>
>>>> and then later feed that to some other reflection algorithm, I suppose. Our implementation will then make the underlying array persistent for the compilation.
>>>
>>> I either cannot find or don’t have access to the revised paper, so apologies if this was discussed therein, but: can you comment on the choice to return a span instead of a non-random-access range object? (I.e. provide a begin() and an end(), but no size(), operator[](size_t), etc.?)
>>
>> It’s more pleasant to work with and I expect it to be more efficient in practice.
>>
>>>
>>> Returning a range would allow implementations to use their existing AST storage, minimizing overhead. Clang stores member declarations as a linked list; to return a span it would have to allocate a declaration’s members additionally as an array somewhere, if those members are reflected.
>>
>> It’s tempting to provide “direct access” to AST storage, but I don’t think it’s feasible in practice. You need a thin bookkeeping layer in between: At the very least, you have to make the entities involved be things the constant-evaluator can deal with (e.g., APValue objects in Clang).
>>
>> So if we are going to have to build that thin layer anyway, we might as well make it maximally convenient and performant for the client code.
>>
>>>
>>> Returning a range would require an additional `meta::info_iterator` type. The user would need to be able to create their own non-const versions of this of course, dereferencing to non-const meta::infos. No problem there, right?
>>
>> It’s certainly feasible. But if you’re going to use a node-based representation in the constant-evaluator, that might add to the computational cost of things.
>>
>> I suppose we could produce “input iterators”, but that has its own downsides.
>>
>>>
>>> The main benefit I can see to requiring random-access storage is that it would help in the implementation of template-for expansions and probably other expansions like splices of parameter packs. E.g. `for(constexpr auto mem : members_of(^S))` is implemented as a sequence of implicit instantiations looking like this:
>>> ```
>>> {
>>> constexpr auto mem = *std::next(0, members_of(^S).begin())
>>> // loop body
>>> }
>>> {
>>> constexpr auto mem = *std::next(1, members_of(^S).begin())
>>> // loop body
>>> }
>>> ...
>>> ```
>>> which, if the underlying storage of members_of(^S) is not random access, requires quadratic instead of linear time.
>>>
>>> But these implementation details are hidden; if that is the only reason for returning a span compilers could instead be asked to deal with the problem on their own.
>>>
>>> Are there other good reasons to return a span instead of a range? Is the ease of use worth the overhead?
>>
>> So I think in most cases the “overhead” is worse for the iterator-based model (which the Lock3 implementation currently uses). A potential exception might be requesting the list of members of a large namespace (currently not a proposed capability) knowing that most won’t be traversed. (If you have to traverse a significant fraction of them, I suspect the span approach wins again). Possibly, this might also depend on the compiler’s architecture and that of its cosntant-evaluator.
>
> Thanks, I am mostly convinced about span.
>
> Re namespaces, does the fact that the number of members can increase between reflections require further specification?

Well we don’t currently propose such a facility (mostly because we haven’t found a compelling use case and it might cause some surprises).

If we did, I’d expect that the answer to your “still error?” questions below would be “yes”.

 Daveed

>
> ```
> namespace foo {
> ... // 10 members
> }
>
> constexpr auto fooMemsA = members_of(^foo);
> fooMemsA[15]; //error
>
> namespace foo {
> ... // 10 more members
> }
>
> fooMemsA[15]; //still error?
>
> constexpr auto fooMemsB = members_of(^foo);
> fooMemsB[15]; //okay
> fooMemsA[15]; //still error?
> ```
>
>>
>> The main reason, though, is that there are fewer restrictions on the algorithms you can apply directly to the reflection results.
>>
>> The original proposal to return vector<info> instead of span<info> was much nicer still, in that regard, because you could mutate things in place. It’s too bad that we don’t have nontransient consteval allocation addressed (and we haven’t progressed much since P0784R7 dropped that).
>>
>> Daveed
>>
>>
>>> (Mainly I just want to ensure this choice has been carefully considered; I’m not necessarily arguing for a change.)
>>>
>>>>
>>>> Also, I’m still hoping that we will have more facilities to have “compile-time variables” as proposed in P0596R1 some day.
>>>>
>>>>>
>>>>> Moreover, if the lifetime of underlying storage returned by meta::members_of is underspecified then implementation of `get_base_types` in the example on page 46 looks quite suspicious. It's not obvious why it's valid to modify span elements inplace:
>>>>>
>>>>> auto result = bases_of(...);
>>>>> for (auto &info : result) {
>>>>> info = type_of(info);
>>>>> }
>>>>> return result;
>>>>
>>>>
>>>> Good catch. That code was added before the transition and I forgot to update it. Thanks again!
>>>>
>>>> Daveed
>>>>
>>>>>
>>>>> IMO it would be more correct to return `vector<info>` instead of `span<info>` until there is no concrete motivation to do the latter.
>>>>>
>>>>> --
>>>>> Andrey Davydov
>>>>
>>>> --
>>>> SG7 mailing list
>>>> SG7_at_[hidden] <mailto:SG7_at_[hidden]>
>>>> https://lists.isocpp.org/mailman/listinfo.cgi/sg7 <https://lists.isocpp.org/mailman/listinfo.cgi/sg7>

Received on 2022-01-20 15:28:27