C++ Logo

liaison

Advanced search

Re: [wg14/wg21 liaison] (SC22WG14.19233) type-generic lambdas

From: Uecker, Martin <Martin.Uecker_at_[hidden]>
Date: Sun, 11 Apr 2021 15:58:20 +0000
Am Sonntag, den 11.04.2021, 16:38 +0200 schrieb Jens Gustedt:
> Martin,
>
> on Sun, 11 Apr 2021 12:55:13 +0000 you ("Uecker, Martin"
> <Martin.Uecker_at_[hidden]en.de>) wrote:
>
> > > No `auto` parameters are need for such a thing. For the proposed C
> > > feature (without `mutable`) this would be
> > >
> > > #define foo(x) \
> > > [&, yy = (x)] { /* type generic argument */ \
> > > auto y = yy; \
> > > ... arbitrary code using y ... \
> > > }()
> > >
> > > This may not be pretty, but here also no `auto` parameters are
> > > needed and the full functionality is already there.
> >
> > Ok, this is fine, but only further confirms that we create
> > a problem for parsers for no good reason.
>
> I don't follow. If not tg, this has only minor problems with parsing
> and perfectly replaces the compound expression feature with a feature
> that is more flexible and better controllable. (I don't have to use
> the `[&]` default of compound expression, for example.)

Yes. Agreed. My point is that the important use cases (like this
one) do not have this problem. So it should be possible to
avoid the issue somehow.

> And for C, the problem is not the adoption of the lambda feature or
> the attribute features alone. The sin has already been committed when
> we integrated the attribute feature with its crude syntax. I was and I
> am still am against that, but WG14 decided otherwise. A convenience
> macro à la `__attribute__` would avoid all of these difficulties.

I think this belongs in the other thread...

> > > The only real and new purpose of type-generic macros with `auto`
> > > parameters is the second usage for them, namely the conversion into
> > > function pointers. A type-generic lambda macro
> > >
> > > #define mysin [](auto x){ \
> > > /* implement a tg formula for the sinus function */ \
> > > return val; \
> > > }
> > >
> > > double (*sin)(double) = mysin;
> > >
> > > is not so easily emulated by gcc's compound statement plus nested
> > > functions, if at all.
> >
> > I agree and this is very nice. Here the parsing problem also does
> > not arise as the type of the pointer converted to is also already
> > known before the lambda is parsed.
>
> yes, exactly
>
> > > > In contrast, with type-generic lambdas:
> > > >
> > > > #define foo [](auto y){ \
> > > > ... arbitrary code using y ... \
> > > > }
> > > >
> > > > the code needs to be parsed without knowing the type of 'y'
> > > > and only then is the argument is parsed which yields this
> > > > information.
> > >
> > > If used in a call expression, yes, in a sense.
> >
> > Yes. But with the syntax it must be possible to parse such
> > expressions. If we can outlaw this, i.e. calling tg lambda
> > directly, this would also solve the problem.
> >
> > > But that problem can also be solved with lookahead for a ballanced
> > > token sequence, finding the arguments to the call (if this is a
> > > call), doing the type inference for the parameters, and then
> > > scanning the body of the lambda for real.
> > >
> > > So yes, this has a cost, but I think that this cost is limited.
> >
> > It is certainly doable. The problem is that this requires unbounded
> > lookahead and also recursively if tg lambdas are nested.
>
> Not much more than the attribute feature, which also parses for
> ballanced token sequences.

Why? The attributes can be parsed and thrown away (ignored)
or processed directly. There is no need to store the token
sequence and revisit it later (potentially an arbitrary
number of times). Or am I missing something?

> > One could implement this as another preprocessing step that shuffles
> > around the tokens before the parser even sees it.
> > But alls this seems like this would be a natural fit for the
> > preprocessor which suffles around tokens anyway.
>
> You mean something like a new pseudo-macro directive similar to `_Pragma`?
> If we suppose that we'd have non-tg lambdas (just to have a syntax)
>
> λ([capture, ...] (auto A, int B, auto C) [[attribute, ...]] { body ... })
>
> Used without following () this could dump the contents in place
>
> [capture, ...](auto A, int B, auto C) [[attribute, ...]] { body ... }
>
> such that it can be converted to a function pointer (or a wide
> function pointer if we have that some day). So a tg-lambda as defined
> in the current paper needs only to be allowed for conversion and not
>
> With () and arguments such as `(a, b, c)` this would then expand to
> something like
>
> [capture, ..., auto A = a, int B = b, auto C = c] [[attribute, ...]] { body ... }()
>
> I don't know if I'd want this, but at least this gives a nice mental
> picture of what is happening, here.

Yes, maybe something like this (see below for some discussion).

> > > The big advantage is that this would bring us feature-complete
> > > type-generic math library (or similar) that enables us to provide
> > > function pointers where they are needed.
> >
> > I agree about the advantages of the general concept.

Let's say I have a tg sin function and I want it to be usable
as a function pointer and also directly as a tg function.

With tg lambdas you could define

#define sin [](auto x){ ... }

and then do (1.)

double (*f)(double) = sin; // conversion

or (2.):

sin(3) // lambda is called directly.

So this is the advantage of the tg lambdas as proposed now
as both cases work and are simple and elegant as they
use the same definition.

But on the other hand this has the disadvantage that the
later case may require some implementation effort and
a naive implementations could be inefficient
(e.g. if the body of the tg lambda is huge, the body or
the argument again include tg lambdas, then this could
quickly explode when naively storing tokens away.)

If we define

#define sin(x) [auto y = (x)](){ ... }

then the later case works even without looking ahead and
we do not need to support directly calling the lambda.
But then the first case (conversion) is not supported.

So I wonder whether this some other way to make this work.

A simple solution is to allow overloading of function-like
and non-function-like macros. Then we could write:

#define sin [](auto x){ ... }
#define sin(x) [auto y = (x)](){ ...}

Maybe the second case could be defined in terms of
the first:

#define sin(x) [auto y = (x)][){ \
  typeof(y) (*c)(typeof(y) z) = sin; \
  return (*c)(y); \
}

But maybe there are better ways to do this, e.g.
a special feature to call tg-lambda with reverse
token sequence:

#define sin(x) reverse_call_op (x) sin

where 'reverse_call_op (x) sin' is the same as
'sin (x)' but then is the only allowed way to call
such a tg-lambda.

> > I just point out the very unfortunate choice of syntax.
>
> That's the whole reason I started this thread, so we are in sync,
> here.

Best,
Martin


Received on 2021-04-11 10:58:29