-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lambdas that modify their closure: &$* #833
Comments
That's it. It's the syntax of capture by pointer in Cpp2. This topic is related to issue #247. |
cpp2 should have a feature for reference capture, maybe captures could even be defined in terms of parameter passing conventions. I don't know why Herb didn't want to support capture by reference, maybe because of safety concerns but the current idiom isn't safer in any way. |
I've noticed the &$* syntax mentioned a couple of other times and really really hated it, and just hoped it would not really need to make an appearance in production code.
I don't think the additional capture block in cpp1 was really all that bad, could we add an optional capture block before or after) the parameter block, and then as was mentioned before, use normal parameter passing syntax to define whether it is a copy or reference?
myLambda : (copy y)$ (x) x + y;
The capture block makes it clearer IMO that values are being captured in a way that might leave them dangling unless you pay attention, also in the case where you use the same captured variable multiple times you can't accidentally capture by copy in one place and reference/pointer in another, unless you really meant to.
There's no reason to prevent normal functions from having a capture block, honestly I'd prefer to have to manually capture global variables in the few cases o use them, to prevent accidental modifications with wide ranging side effects, and to make wide ranging side effects really visible in the code.
I know a lot of people are pushing for terser and terser syntax at the moment, but for the most part, C++ is a complex language, not a scripting language, and I'd rather type a few more characters if it added nice signposts to my code, allowing me to parse it visually more easily, and flag important things where they are happening.
On 15 November 2023 07:48:29 Abhinav00 ***@***.***> wrote:
cpp2 should have a feature for reference capture, maybe captures could even be defined in terms of parameter passing conventions. I don't know why Herb didn't want to support capture by reference, maybe because of safety concerns but the current idiom isn't safer in any way.
—
Reply to this email directly, view it on GitHub<#833 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQI4FMEUYVGBRD74DGDYERXUVAVCNFSM6AAAAAA7L7UFDKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMJRHE2TQMZTGM>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
We can have the best of both worlds. Also, |
:(x) x + y$ is certainly terse, but does it scan easily in a large codebase as a lambda?
Also this limits capture to copy only, or at least be defined by the author, rather than allow the compiler to decide / optimise.
Take this made up syntax for example, if we allowed adding captures to the parameter list to signal that they follow the same rules (the in is default but included to emphasise what would be possible)
:(x, in y$) x + y
Might require double naming, but would give the author the ability to signal intent, rather than implement specifics. Here the compiler can decide whether it is better to capture by reference or copy.
If I need to make sure the captured variables stay in scope I can specify move or copy as required. Also changing the name can sometimes be beneficial,
:(x, in y$ = someReallyLongName) x + y / y ^ y
As to your mention of performance, I don't see how postfix $ is changing performance in your example, it is identical to cpp1 [t = o.type()], and so would perform the same right?
On 15 November 2023 13:42:56 Johel Ernesto Guerrero Peña ***@***.***> wrote:
I know a lot of people are pushing for terser and terser syntax at the moment, but for the most part, C++ is a complex language, not a scripting language, and I'd rather type a few more characters if it added nice signposts to my code, allowing me to parse it visually more easily, and flag important things where they are happening.
We can have the best of both worlds.
The lack of ceremony also means that what is relevant stands out more.
I'm a fan of :(x) x + y$, as the parameterized nature of the expression stands out more.
Also, post(size() == size()$ + 1) is fantastic.
You don't need to give size() a name and then use it.
And the o.type()$ in sfml_types.std::ranges::any_of(:(x) o.type()$.starts_with(x)) (#789 (reply in thread)<#789 (reply in thread)>)
is performant; it is captured once and used N times.
—
Reply to this email directly, view it on GitHub<#833 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQLDGEXWMBX3KAWXPRTYETBF3AVCNFSM6AAAAAA7L7UFDKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMJSGU2TSMRVG4>.
You are receiving this because you commented.Message ID: ***@***.***>
|
That's right. |
Capture the function expression instead (https://cpp2.godbolt.org/z/zne45Pb6o):
|
I recall reading the same comment on Cpp1 lambdas. |
You've just referenced two cases where the optimizer handles something, as proof that the optimizer isn't always useful?
I'm making the same argument for capture that has been made for parameter passing. I the author want to program my intent, not fill my code with symbols to handle the exact type shenanigans required to incant it...
On 15 November 2023 15:38:11 Johel Ernesto Guerrero Peña ***@***.***> wrote:
Also this limits capture to copy only, or at least be defined by the author, rather than allow the compiler to decide / optimise.
I recall reading the same comment on Cpp1 lambdas.
As Cpp2 demonstrates, explicit is better than implicit.
There are examples in Cpp1 where "leaving it to the optimizer" has not proven itself for ranges of users.
I recall TDEH<https://quuxplusone.github.io/blog/2019/08/02/the-tough-guide-to-cpp-acronyms/#eh-tdeh> and HALO<https://quuxplusone.github.io/blog/2019/08/02/the-tough-guide-to-cpp-acronyms/#halo>.
Please, share the others.
—
Reply to this email directly, view it on GitHub<#833 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQNAM6MR6OXMHHKIYR3YETOV7AVCNFSM6AAAAAA7L7UFDKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMJSG43DKOJQGQ>.
You are receiving this because you commented.Message ID: ***@***.***>
|
As proof that relying on optimizations to make a feature viable in contexts where performance matters isn't a recipe for success.
OK, that's much better. |
We already have the syntax in Cpp2.
|
That formulation doesn't really work for
|
Perhaps it'd be better to just allow function expressions to be parameterized just like blocks.
|
This one is a big change, so I'd really like to hear what Herb has to say and what his rationale was behind the original decision along with if he thinks the change proposed here are worth it. But since it's the syntax talk, I'll just propose something. Also, this idea was originally for marking a : = 9;
f(x&, 8); //first argmument is inout
//works very well with UFCS
x&.f(3);
//instead of the current
(inout x).f(7); Making mutation explicit helps and I just think it's a great thing that Hylo has. Note: As for the symbol used, either another symbol could be used (~ comes to mind) or cpp2 can have something different for taking address of a variable (maybe just recommend using std::addressof) or have something similar to sizeof (like Sorry as this got off-topic but just wanted to throw it out there. |
Or just take the hit of having one declaration per capture, with pointers for
But that is sub-optimal, as you make copies of the captures: auto main() -> int{
auto i {1};
static_cast<void>([_0 = (&i), _1 = std::move(i)]() -> auto{
auto j {_0};
auto k {_1};
return ++*cpp2::assert_not_null(std::move(j)) + std::move(k);
});
} A block parameter isn't better (https://cpp2.godbolt.org/z/36Esj66s9):
auto main() -> int{
auto i {1};
{
auto const& j = &i;
static_cast<void>([_0 = std::move(i), _1 = j]() -> auto{
auto k {_0};
return ++*cpp2::assert_not_null(_1) + std::move(k);
});
}
} I think this argues in favor of #833 (comment) |
@JohelEGP I think having one declaration per capture as you have suggested, is the right way. Cpp2 compiler can optimize and avoid the copy by making them a real alias. In a short lambda body, the programmer can directly write var1: = 10;
call(: () = {
print(var1&$* + var1&$*);
}); But in a long lambda body, the programmer can optionally make aliases to captures: var1: = 10;
call(: () = {
// We can use the same variable name `var1`, because it's in a different scope.
var1: = var1&$*;
print(var1 + var1);
}); Every If this optimization is not acceptable as the programmer expects |
For the alias syntax (in a way that the new name will be replaced with the old name, similar to a macro): abc: namespace alias = some::long::name;
v32: type alias = std::vector<i32>;
fnc: (x) -> i32 alias = /* any expression */;
var: i32 alias = /* any expression */;
For example: var1: = 10;
// The type is optional in variable alias declarations.
var2: alias = var1 + 10;
print(var2);
// It generates the following Cpp1 code:
// print(var1 + 10); Or something similar to this approach, to avoid the copy when we declare a variable to capture. |
abc: namespace alias = some::long::name;
v32: type alias = std::vector<i32>;
fnc: (x) -> i32 alias = /* any expression */;
var: i32 alias = /* any expression */; In this example, function aliases are simply Forced Inline Functions in terms of Cpp1. And variable aliases are like function aliases without parameters. |
For some context, we're talking about these declarations
I did considered this, and thought of two things. First, the need to prove that it doesn't make a performance difference. Second, the compiler could recognize that up-front variables are captures. That said, your comment made me think that the status-quo might be fine.
However, I still think
It looks to me that not having a dedicated place for declaring captures Copies become more important once you can have I certainly like the current status of capturing at the point of use. |
My favorite |
Closing in favour of #247. |
This is no longer the case since commit 4bd0c04.
|
When porting lambdas that modify their closure from Cpp1
to Cpp2 the syntax gets pretty ugly
This &$* was the only way I found to make it work.
When doing the same for nested lambdas
it gets even worse
This nested lambda example may seem artifical, but appeared when porting a real Cpp1 program over to Cpp2.
Is there any better syntax for this available? Do I miss something?
The text was updated successfully, but these errors were encountered: