C++ Logo

sg7

Advanced search

Re: [SG7] Necessity of the unquote operator %{}

From: David Vandevoorde <daveed_at_[hidden]>
Date: Wed, 24 Mar 2021 18:20:16 -0400
> On Mar 24, 2021, at 5:09 PM, David Rector <davrec_at_[hidden]> wrote:
>
>
>
>> On Mar 24, 2021, at 4:19 PM, David Vandevoorde <daveed_at_[hidden] <mailto:daveed_at_[hidden]>> wrote:
>>
>>
>>
>>> On Mar 24, 2021, at 2:02 PM, David Rector <davrec_at_[hidden] <mailto:davrec_at_[hidden]>> wrote:
>>>
>>> While that does seem like it could be a remotely conceivable usage, which might indeed be absolutely necessary to do truly expert things along the lines of recursively injecting into other fragments, the vast majority of usages of [::] or |##| within a fragment will refer to parameters/local variables declared in the consteval context *enclosing* the fragment (as opposed to within the fragment, as in your example).
>>>
>>> It would be nice if that vast majority of usages did not require %{}.
>>>
>>> A text search over "%" in P2237 shows such usages, which seem to be all the typical uses within fragments.
>>>
>>> Text searches over "|" find examples of || (now [::]) and |##|; there are a few that occur without %{}, however all of these are in a consteval context, rather than within a fragment. E.g. in section 6.1.1:
>>> ```
>>> template<class_type T, structural_subtype_of<T> U>
>>> void structural_copy(const T& src, U& dst) {
>>> constexpr auto members = meta::data_members_of(reflexpr(src));
>>> template for (constexpr meta::info a : members) {
>>> constexpr meta::info b = meta::lookup(dst, meta::name_of(a));
>>> dst.[:b:] = src.[:a:];
>>> }
>>> }
>>> ```
>>>
>>> The following proposal seems to me the best solution, which would eliminate %{} from all examples in P2237, while still permitting unusual usages:
>>> (Typical case) Lookup for any declaration referred to in a [::], |##|, or << that is *not enclosed in %{}* will begin in the *most immediate enclosing consteval context*
>>
>> Can you elaborate what that means? I have several difficulties in understanding it:
>> - what is a “declaration referred to in a [::]”?
>> (but it feels like a chicken-and-egg issue: how can I know what declaration is referred to
>> if I still have to select how I will look up declarations?)
>> - what is a “consteval context”?
>>
>> Daveed
>
> Haven’t fully thought this out, but it seems to me the notion of a "consteval context" is necessarily introduced by fragments, e.g.:
> ```
> consteval {
> // "consteval context"
> auto foo = <namespace {
> // "non-consteval context"
> consteval void f() {
> // "consteval context"
> auto bar = <class {
> // "non-consteval context"
> //...
> }>;
> }
> }>;
> }
> ```

Okay; I think I understand what you mean by “consteval context”.


> Whenever we refer to a declaration inside an injection/splicing mechanism, i.e. `[:x:]`, `|#x#|`, or `<< x`, it is reasonable to expect that by `x` the user is referring to an object produced during constant evaluation, rather than e.g. a non-consteval declaration which happens to be a member of the fragment introduced prior to the splice we are performing. So, I think lookup should just always skip over any non-consteval context it is presently in, if any, to get to the enclosing consteval context wherever a candidate resides in both contexts. (Though, see caveat after example below.)

That seems quite ad-hoc to me. Let me adapt your structure above and add declarations and a use for x:

```
consteval {
  // "consteval context"
  info x = …; // (1)
  auto foo = <namespace {
    // "non-consteval context”
#ifdef SWITCH
    consteval
#endif
    void f() {
      // "consteval context” (maybe)
      info x = …; // (2)
      typename[:x:] z{}; // Finds (1) or (2) depending of whether f() is consteval
      auto bar = <class {
        // "non-consteval context"
        //...
      }>;
    }
  }>;
}
```

IIUC, the x in typename[:x:] find different declarations depending on whether f() is declared consteval or not.
I personally think that’s untenable.

It’s also highly non-orthogonal and in my experience such ad-hoc design decisions come back to haunt us.

I much prefer Andrew & Wyatt’s mechanism where there is an orthogonal mechanism (“unquote”) to change the current expression context, leaving the semantics of splicers context-independent.

 Daveed

>
> So, I would propose that any reference to a declaration in an injection/splicing mechanism should begin lookup in the nearest enclosing consteval context — and I suppose should also jump over any enclosing non-consteval context if it doesn’t find it in the first.
>
> The user could achieve different behavior, in the comparatively rare cases in which it is necessary, via %{} within those contexts; e.g.
>
> ```
> consteval {
> //consteval context
> meta::info bar = …; //1
> auto foo = <namespace {
> // non-consteval context
> // but bar is a constexpr variable, so okay
> constexpr meta::info bar = …; //2
> << bar; //injects 1
> << %{bar}; //injects 2
> }>;
> }
> ```
> Though, considering the above example, it could be argued that even `<< bar` should inject #1; i.e. rather than considering "consteval contexts", we should just consider lookup on constant-evaluated declarations within any context. But if that is not desirable this would be a way to still put %{} to work.
>
> And more fine-grained lookup control can presumably be accomplished via that meta::lookup function if I understand it correctly, e.g.
> ```
> meta::info b = meta::lookup(ctx, "bar");
> while (!sometest_re_context(b))
> b = meta::lookup(meta::parent_of(b), "bar");
> ```
> Or something along those lines.
>
>>
>>
>>> Thus [:b:] and [:a:] in `structural_copy` are okay, since they are referenced in a consteval context, and thus lookup begins in the current scope as usual.
>>> But for most [::] etc usages within fragments, the most immediate enclosing consteval context is that containing the fragment definition, which is where the parameters/local variables of interest for typical injection tasks usually reside — i.e. see the "property" metafunction in Section 7 of P2237; all the %{} there can be eliminated under this rule.
>>> But if you are, say, injecting "structural_copy" into a fragment, lookup for [:a:] and [:b:] would again begin in their current scope, since it is consteval.
>>> (Unusual case) Lookup for any declaration referred to *within %{}* is guaranteed to begin in the current context, regardless of whether or not it is a consteval context.
>>>
>>> Note too that the `structural_copy` example happens to reference a `meta::lookup(…)` function which should help the user in still more difficult/fine-grained lookup needs — manually skipping over certain contexts, etc.
>>>
>>>> On Mar 24, 2021, at 11:34 AM, David Vandevoorde <daveed_at_[hidden] <mailto:daveed_at_[hidden]>> wrote:
>>>>
>>>>
>>>>
>>>>> On Mar 23, 2021, at 6:46 PM, David Rector via SG7 <sg7_at_[hidden] <mailto:sg7_at_[hidden]>> wrote:
>>>>>
>>>>> I’ve come around on most of Andrew’s syntax: [::] seems really to be better than $ after trying it out, considering all the parentheses that would be needed in typical usages, etc.
>>>>>
>>>>> But there remains a complexity from P2237 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2237r0.pdf <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2237r0.pdf>) which I am having trouble understanding. Section 7 contains extensive usage of the "unquote operator" %{}, and discussion of it, particularly in 7.1.7.
>>>>>
>>>>> Here is the relevant section in its entirety, adjusted to use P2320 syntax (i.e. [::] replaces ||):
>>>>> > ### 7.1.7 Unquote
>>>>> > The unquote operator allows the use of local variables and expressions involving local variables inside a fragment. For example:
>>>>> > ```
>>>>> <page31image1074358928.png>
>>>>> > consteval meta::info make_fragment(meta::info t) {
>>>>> > return <struct {
>>>>> > typename [:%{t}:] obj;
>>>>> > }>;
>>>>> > }
>>>>> > ```
>>>>> > The function returns a fragment that includes a splice involving the parameter t. Within the fragment, the unquote operator designates a placeholder for a constant expression. Because an unquoted expression is a placeholder, it is type-dependent, meaning that we need to write typename before the declaration. The value for that placeholder is computed during constant expression evaluation as part of the fragment value and is replaced by its corresponding value during injection.
>>>>> >
>>>>> > The unquote operator can also include more complex expressions:
>>>>> > ```
>>>>> > consteval meta::info make_fragment(meta::info t) {
>>>>> > return <struct {
>>>>> > typename [:%{meta::add_pointer(t)}:] ptr;
>>>>> > }>;
>>>>> > }
>>>>> > ```
>>>>> > The resulting fragment will inject a pointer to the type reflected by t into the program.
>>>>>
>>>>> Even with this explanation, I still do not understand the need for %{}. As an lvalue `t` is already a placeholder for a value, and in each call to `make_fragment` it will already be replaced during constant evaluation with a particular rvalue. What work does %{} do beyond that? What am I missing?
>>>>>
>>>>> More to the point:
>>>>> a) Can Andrew & co present an example of an unquote operator usage in which it is plainly necessary, i.e. where the user’s intent is not clear without it?
>>>>> b) Might we adjust the syntax to say the unquote operator is not strictly needed wherever the intent of the user is unambiguous without it?
>>>>
>>>> The unquote operator affects lookup . Consider:
>>>>
>>>> ...
>>>>
>>>> auto frag = <namespace {
>>>> X* begin(Y) { ... } // (1)
>>>> bool algo(typename[:%{begin(rs)}:] start) { ... }
>>>> // This `begin` is guaranteed never to find (1).
>>>> };
>>>>
>>>> Daveed


Received on 2021-03-24 17:20:25