Skip to content

Commit

Permalink
Emit anonymous functions as constexpr or mutable lambdas
Browse files Browse the repository at this point in the history
See comment thread for previous commit:
4bd0c04
  • Loading branch information
hsutter committed Dec 3, 2023
1 parent bc82833 commit b9e73e0
Showing 1 changed file with 7 additions and 2 deletions.
9 changes: 7 additions & 2 deletions source/to_cpp1.h
Original file line number Diff line number Diff line change
Expand Up @@ -4421,10 +4421,15 @@ class cppfront
emit(*n.parameters);
}

// For an anonymous function, make the emitted lambda 'mutable'
// For an anonymous function, the emitted lambda is 'constexpr' or 'mutable'
if (!n.my_decl->has_name())
{
printer.print_cpp2( " mutable", n.position() );
if (n.my_decl->is_constexpr) {
printer.print_cpp2( " constexpr", n.position() );
}
else {
printer.print_cpp2( " mutable", n.position() );
}
}

// For now, adding implicit noexcept only for move/swap/dtor functions
Expand Down

8 comments on commit b9e73e0

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hsutter I'm wondering what you think about this possibility (https://cpp2.godbolt.org/z/jz5sx6exc):

main: () = {
  int_generator := (:() 0$++);
  (copy i := int_generator())
    while i != 10 next i = int_generator() {
      std::cout << "(i)$ ";
    }
}

It's something that was possible before, albeit with the grawlix (#247):

    (copy i := 0u)
      Perfects(3)(:(x: int) = {
        expect(x.eq(res$[i&$*]));
        i&$*++;
      });

For #741 (comment), I have transformed from the Cpp1 (https://cpp2.godbolt.org/z/evj8PnzE7):

auto scan_left(auto rng, auto init, auto op) {
    return transform(rng, [first = true, acc = init, op](auto e) mutable {
            if (first) first = false; 
            else acc = op(acc, e);
            return acc;
        });
}

To the Cpp2 (https://cpp2.godbolt.org/z/Yjodo7GdE):

scan_left: (copy rng, init, op) rng.transform(:(e) -> _ = {
  if (true$) {
    true$ = false;
  } else {
    init$ = op$(init$, e);
  }
  return init$;
});

@hsutter
Copy link
Owner Author

@hsutter hsutter commented on b9e73e0 Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting.

Re 0$: Somehow I don't think I thought of capturing a literal. That is useful... but I worry slightly that capturing a literal twice with the meaning that it refers to the same captured variable may be brittle because of being sensitive to misspellings, and that capturing the same literal always means the same shared variable (to capture 0$ for two different uses requires naming a variable in scope). But it does seem useful here, though probably less readable than naming first explicitly.

Other initial reactions to this code:

  • int_generator looks cool (but I want to investigate why the ( ) are needed, likely just a needs a parsing tweak to make it work without those)
  • true$ = false; was a little jarring at first reading, and took me two readings to get used to, then it didn't seem jarring anymore
  • if true$ { true$ = false; } else ... could be an emergent idiom/style
  • using init$ repeatedly as a captured variable name is interesting
  • I had to look up what "grawlix" meant :)

Thanks for pointing these out!

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(to capture 0$ for two different uses requires naming a variable in scope)

I think I have used something like 0$ and (0)$ for that.

  • int_generator looks cool (but I want to investigate why the ( ) are needed, likely just a needs a parsing tweak to make it work without those)

I think that's #479, which should be ready to merge.

@hsutter
Copy link
Owner Author

@hsutter hsutter commented on b9e73e0 Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the point of being able to (re)name the capture variable: I suspect using a statement-scope parameter may become an emergent idiom -- at least, it's the first thing I tried!

(first := true) rng.transform(:(e) -> _ = {
if first$ {
    first$ = false;
} else {
    init$ = op$(init$, e);
}
return init$;
});

I think I have used something like 0$ and (0)$ for that.

I think that scares me a little. :) Perhaps the naming idiom reduces the need for that? The only problem with putting it on a declaration like

(val := 0) int_generator := (:() val$++);

is that in the current implementation the name int_generator is then hidden in an introduced scope and can't be used later in the function.

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.
And for return values, you need to opt out of the terse syntax, and write
-> _ = { (first := true) return …; }.

As #833 (comment) hints at,
we can already use a statement parameters block for the whole function body.
But that's blocked on #832 (https://cpp2.godbolt.org/z/88MY316qz):

scan_left: (copy rng, init, op) rng.transform(:(e) -> _ = {
  (copy first := true$, copy acc := init$) {
    if (first) {
      first = false;
    } else {
      acc = op$(acc, e);
    }
    return acc;
  }
});

The current syntax does mean that we need still the function body block.

@JohelEGP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(copy first := true$, copy acc := init$) {

Those should be inout to alias the capture.

@JohelEGP
Copy link
Contributor

@JohelEGP JohelEGP commented on b9e73e0 Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  if (true$) {
    true$ = false;
  } else {

I translated it too literally (I even left the parentheses in).
I normally use std::exchange (https://cpp2.godbolt.org/z/e39ao5rjY): if false$.std::exchange(true) {.
But that ICEs on Clang (llvm/llvm-project#73418) and you know GCC (#746 (comment)).

This first idiom is common in cppfront's code base.
I have found myself using it, and have put some thought into it.

Ones of my whims has been to introduce a once_flag that automatically does the std::exchange.
But when I consider it from the PoV of cppfront's code base, I feel resistant.
Using it loses symmetry with the places that use first again somewhere else in the loop (not my use case, yet).
It can also be misused in that same case by using the flag again before the next iteration.

However, once_flag works much better now in a function expression (https://cpp2.godbolt.org/z/9E39aq7fs): if (!once_flag()$) {.
Although the previous negatives still apply, and a new one is that it only works so well in a function expression.
It's still a great utility if you only need the first idiom in function expressions.

The change in model makes me jealous of function expressions.
Now I feel like lending a bigger ear to the proponents of "lambdas everywhere"
(i.e., replacing functions with lambdas at namespace scope).

@jcanizales
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int_generator := (:() 0$++);

I want to investigate why the ( ) are needed

I like the "lambdas without parameters are a little monkey" side of it. monkey := (:() body);

Please sign in to comment.