C++ Logo

SG7

Advanced search

Subject: Re: string. vector vs string_view, span in reflection API
From: David Vandevoorde (daveed_at_[hidden])
Date: 2021-04-12 15:24:47


> On Apr 9, 2021, at 9:09 PM, Matus Chochlik via SG7 <sg7_at_[hidden]> wrote:
>
> On Sat, Apr 10, 2021 at 2:20 AM David Rector <davrec_at_[hidden] <mailto:davrec_at_[hidden]>> wrote:
>
>
>> On Apr 9, 2021, at 8:35 AM, Matus Chochlik via SG7 <sg7_at_[hidden] <mailto:sg7_at_[hidden]>> wrote:
>> This still happens at compile-time inside of the compiler, so it could allocate the filtered range of metaobjects by using some internal mechanism, that is not exposing generic "dynamic" allocation in consteval.
>> Just saying, in case solving dynamic allocation in constexpr/consteval contexts is something blocking us moving forward with reflection.
>> Or am I missing something?
>>
>
> To go further, why should the API include something that requires these allocations in the first place, since they don’t seem particularly efficient (or at least won’t scale well) even if the compiler can handle them internally?

Note that in terms of our current constant-evaluator, dynamic memory allocation is relatively cheap compared to the overhead of interpretation (because the allocation is just a call into the compiler’s allocator, whereas, e.g., interpreting a user-written loop involves a fair bit of work, particularly if the loop implies things like pointer arithmetic and indirections, and in the case of a range-based-for: hidden function calls).

>
> That is, should we really be encouraging users to use things like `meta::members_of(reflection, predicate)`, which always necessitates a new allocation?
>
> It would be more efficient, and permit more natural programming, to only expose `meta::members_of(reflection)` (i.e. no predicate argument) and instead encourage the user to condition actions on those members via control flow statements during iteration over the compiler’s already-allocated container of members.
>
> This would require expanding the contexts in which `consteval {}` metaprograms are permitted, and introducing corresponding fragment kinds to permit injection of entities in those contexts.
>
> That is the precisely the argument of this paper: https://isocpp.org/files/papers/P2353R0.html <https://isocpp.org/files/papers/P2353R0.html>, a cleaned up version of the paper “The syntactic gap” I shared on here awhile ago, which should appear in the next mailing.
>
> Matus here is how I would write your `unpack_range`, using P2353 features:
>
> ```
> template <std::experimental::meta::info MO>
> consteval auto unpack_range() {
> return unpacked_range<
> consteval {
> template for (auto mem : meta::members_of(MO))
> if (meta::is_constructor(mem))
> << ^<frag::targ>{ mem }; //a template argument fragment
> }
> >{};
> ```
>
> This requires no allocations and is simple to read and write.
>
> Something to consider moving forward, given the emphasis on efficiency.
>
> I'm just guessing about Andrew and Wyatt's or Daveed and Faisal's motivations here, but based on my reflection implementation experience I'd say the reason why we want to do some of the filtering inside of the compiler might be that "materializing" the whole:
>
> meta::members_of(MO)
>
> that can then be filtered with arbitrary consteval C++ code may be quite "heavy" for some meta-objects.

Right. In particular, if the “filter” consists of intrinsics, the whole thing can be done at near native speed, whereas I estimate a user-written loop to do the same will be two or three orders of magnitude slower.

The trade-offs are likely to change a bit over time as we improve our evaluators, but I’m not sure how quickly or how well we’ll be able to reduce the gap.

The `template for` in the example above is interesting. In terms of compilation cost I think of three tiers:
        1) fastest: intrinsics (e.g., members_of (potentially))
        2) medium: constant-evaluation of “user code” (e.g., “range-based-for”)
        3) slowest: instantiation (e,g, “template for”)

If you don't have filters on members_of, you’re _probably_ better off rewriting the template-for as:

        template for (auto mem :
                        ranges::remove_if(meta::members_of(MO), !is_constructor))
            << ^<frag::targ>{ mem }; //a template argument fragment

(or something like that) because that moves some of the previous iterations of template-for (tier 3) into the tier 2 category.

However, those iterations can go into tier 1 (assuming a slightly optimized members_of with filter) using:

        template for (auto mem : meta::members_of(MO, is_constructor))
            << ^<frag::targ>{ mem }; //a template argument fragment

(All that is just my estimates.)

        Daveed

> for example if:
>
> auto MO = ^std;
> //or
> auto MO = ^boost;
>
> and I do think that reflecting namespaces and getting (some of) their members has valid and useful use-cases.
> When I did the POC implementation for what became the TMP-based reflection TS I had metaobject-sequences (or ranges) be meta-objects themselves and instantiated the elements lazily. It worked with classes, etc. but I don't have much data for reflected namespaces.
>
> --Matus
>
> --
> SG7 mailing list
> SG7_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg7



SG7 list run by sg7-owner@lists.isocpp.org

Older Archives on Google Groups