This paper proposes to remove two minor stumbling blocks to the teachability of lambda syntax — stumbling blocks of which many programmers may not even be aware.
The first is that[&x, =](){ return x; }
is ill-formed.
The second is that[=, y](){ return x; }
is ill-formed.
I propose to make both of these lambdas well-formed, with the obvious meanings.
&
(to capture by reference) at the beginning of your capture list."As for making redundancy illegal, I believe that if you write something redundant, then you've clearly made some kind of mistake. If you use a default capture, and you then capture a variable in the same way as the default capture, then you are perhaps confused as to what you meant to capture and how you meant to capture it.
As for making redundancy illegal, I believe that if you write something redundant, then you've clearly made some kind of mistake. If you use a default capture, and you then capture a variable in the same way as the default capture, then you are perhaps confused as to what you meant to capture and how you meant to capture it.I think the problem OP is trying to solve is that he wants to capture a variable by-copy that isn't actually odr-used (or really, used by any definition) in the lambda. So [=] won't capture it, but [=, token] is ill-formed.
On Tue, Jul 18, 2017 at 10:01 AM, Nicol Bolas <jmck...@gmail.com> wrote:On Tuesday, July 18, 2017 at 12:19:37 PM UTC-4, Barry Revzin wrote:As for making redundancy illegal, I believe that if you write something redundant, then you've clearly made some kind of mistake. If you use a default capture, and you then capture a variable in the same way as the default capture, then you are perhaps confused as to what you meant to capture and how you meant to capture it.I think the problem OP is trying to solve is that he wants to capture a variable by-copy that isn't actually odr-used (or really, used by any definition) in the lambda. So [=] won't capture it, but [=, token] is ill-formed.
In that case, I think the `x=x` form would make it more clear what you're trying to do. It demonstrates that you're not merely capturing the variable for use in the lambda; you're explicitly and deliberately copying it into the members of the object.Yep, that's covered in the paper.In re:> It's a simple rule to remember: put the default first. I don't think that rule affects teachability.I'd like to add that sentence to the paper, somewhere, so that I can rebut it.Currently C++ supports "defaults" or "default-like behavior" in the following places that I'm aware of:- Default-valued function arguments (only at the end)- C-style variadic "do this with all the unmatched arguments" (only at the end)- Variadic template parameter packs (anywhere, but typically you want to put them at the end)- Aggregate initializer "set all other fields to zero" (only at the end)- Default-valued template parameters (anywhere)- Non-static data members (anywhere)- Lambda captures (only at the beginning)
Am I missing anyplace in C++ that does follow the simple rule "put the default first"? It seems to me that that "rule" is more of a special case that changes the general rule only in the case of lambda-captures — which is why I'm proposing to change it, to bring capture-lists into closer harmony with the rest of the language.
In many of the above cases, there's a grammatical reason that they can appear only at the end of the construct. For the cases where there's no grammatical reason to put them at the end, C++ allows you to put them anywhere. — and then there's lambda-captures, which currently work "backwards" from the rest of the language.
Does anyone happen to know the exact date of the pre-Albuquerque mailing submission deadline? I was hoping to see it mentioned in N4633 but nope.
On Tuesday, August 15, 2017 at 2:29:27 PM UTC-4, Avi Kivity wrote:On 07/17/2017 09:46 PM, Arthur O'Dwyer wrote:
This paper proposes to remove two minor stumbling blocks to the teachability of lambda syntax — stumbling blocks of which many programmers may not even be aware.
The first is that[&x, =](){ return x; }
is ill-formed.
The second is that[=, y](){ return x; }
is ill-formed.
I propose to make both of these lambdas well-formed, with the obvious meanings.http://quuxplusone.github.io/draft/liberal-syntax-for-lambdas.html
Comments welcome, either here or via email. Remember to read the paper first, though!
I would like to re-raise [&&x] as a shortcut for [x = std::move(x)] () mutable. It is seen extensively in continuation passing code, and since the language has first-class support for moves, lambdas should too.
One could argue for the `&&x -> x = std::move(x)` part. But the automatic `mutable`-izing part? No. Whether you agree with the `const`-by-default nature of lambdas or not, we shouldn't create syntax that arbitrarily changes the nature of a lambda.
Also, do you really want to move from `x`?
Or do you want to do a decay-copy into `x`? Because the latter is what `std::thread/async` do. And we already have a proposal for shortening that.
On 08/15/2017 10:03 PM, Nicol Bolas wrote:
On Tuesday, August 15, 2017 at 2:29:27 PM UTC-4, Avi Kivity wrote:On 07/17/2017 09:46 PM, Arthur O'Dwyer wrote:
This paper proposes to remove two minor stumbling blocks to the teachability of lambda syntax — stumbling blocks of which many programmers may not even be aware.
The first is that[&x, =](){ return x; }
is ill-formed.
The second is that[=, y](){ return x; }
is ill-formed.
I propose to make both of these lambdas well-formed, with the obvious meanings.http://quuxplusone.github.io/draft/liberal-syntax-for-lambdas.html
Comments welcome, either here or via email. Remember to read the paper first, though!
I would like to re-raise [&&x] as a shortcut for [x = std::move(x)] () mutable. It is seen extensively in continuation passing code, and since the language has first-class support for moves, lambdas should too.
One could argue for the `&&x -> x = std::move(x)` part. But the automatic `mutable`-izing part? No. Whether you agree with the `const`-by-default nature of lambdas or not, we shouldn't create syntax that arbitrarily changes the nature of a lambda.
I see your point. How about, &&x translates to x = std::move(x) _and_ x is a mutable member of the synthetic struct, even if `mutable` is not specified?
Also, do you really want to move from `x`?
I really do want to move from x. I have thousands of lines doing that, though I expect a significant reduction when we start using coroutines.
Or do you want to do a decay-copy into `x`? Because the latter is what `std::thread/async` do. And we already have a proposal for shortening that.
This is wonderful. How about, in addition, a unary &&? &&x == std::move(x). std::move(x) is so common it deserves some syntax.
[](auto&& x) { return f(std::forward<decltype(x)>(x)); }could after P0428 be written with named template parameters as[]<class T>(T&& x) { return f(std::forward<T>(x)); }
Admittedly this is not as short as P0644's syntaxx => f(>>x)but I think it's more friendly to standardization than either of those, and at least the argument to std::forward wouldn't be gratuitously different from the usual one, anymore.
On Wednesday, August 16, 2017 at 4:47:32 AM UTC-4, Avi Kivity wrote:On 08/15/2017 10:03 PM, Nicol Bolas wrote:
On Tuesday, August 15, 2017 at 2:29:27 PM UTC-4, Avi Kivity wrote:On 07/17/2017 09:46 PM, Arthur O'Dwyer wrote:
This paper proposes to remove two minor stumbling blocks to the teachability of lambda syntax — stumbling blocks of which many programmers may not even be aware.
The first is that[&x, =](){ return x; }
is ill-formed.
The second is that[=, y](){ return x; }
is ill-formed.
I propose to make both of these lambdas well-formed, with the obvious meanings.http://quuxplusone.github.io/draft/liberal-syntax-for-lambdas.html
Comments welcome, either here or via email. Remember to read the paper first, though!
I would like to re-raise [&&x] as a shortcut for [x = std::move(x)] () mutable. It is seen extensively in continuation passing code, and since the language has first-class support for moves, lambdas should too.
One could argue for the `&&x -> x = std::move(x)` part. But the automatic `mutable`-izing part? No. Whether you agree with the `const`-by-default nature of lambdas or not, we shouldn't create syntax that arbitrarily changes the nature of a lambda.
I see your point. How about, &&x translates to x = std::move(x) _and_ x is a mutable member of the synthetic struct, even if `mutable` is not specified?
No. There's no reason to implicitly assume that, if a user wants to move an object into the lambda, they also mean to modify it. These are two orthogonal concepts.
If you move a `unique_ptr` into a lambda, that doesn't mean you're going to modify the `unique_ptr` *itself*. You may modify what it points to, but `unique_ptr` doesn't forward `const` through to `get`.
Also, do you really want to move from `x`?
I really do want to move from x. I have thousands of lines doing that, though I expect a significant reduction when we start using coroutines.
My question is why do you want to move there instead of decay-copying?
Or do you want to do a decay-copy into `x`? Because the latter is what `std::thread/async` do. And we already have a proposal for shortening that.
This is wonderful. How about, in addition, a unary &&? &&x == std::move(x). std::move(x) is so common it deserves some syntax.
`std::move(x)` is sufficiently short that it doesn't need to be given special syntax. It's 9 characters. By contrast, "std::forward<decltype(x)>(x)` is 24 characters, not counting the size of the `x` variable.
But I don't want to see "implicit mutable" in the language at this point, either. I'm certain that "mutable" should have been the default in 2011, but now that ship has been sailed for 6 years; we can't get it back.
>> This is wonderful. How about, in addition, a unary &&? &&x ==
>> std::move(x). std::move(x) is so common it deserves some syntax.
>
> `std::move(x)` is sufficiently short that it doesn't need to be given
> special syntax. It's 9 characters. By contrast,
> "std::forward<decltype(x)>(x)` is 24 characters, not counting the size of
> the `x` variable.
Today I learned that Louis Dionne has a highly plausible proposal for making "std::forward" lambda syntax consistent with the old non-lambda syntax.
[](auto&& x) { return f(std::forward<decltype(x)>(x)); }
could after P0428 be written with named template parameters as
[]<class T>(T&& x) { return f(std::forward<T>(x)); }
Admittedly this is not as short as P0644's syntax
x => f(>>x)
but I think it's more friendly to standardization than either of those, and at least the argument to std::forward wouldn't be gratuitously different from the usual one, anymore.
On 08/16/2017 06:08 PM, Nicol Bolas wrote:
On Wednesday, August 16, 2017 at 4:47:32 AM UTC-4, Avi Kivity wrote:On 08/15/2017 10:03 PM, Nicol Bolas wrote:
Also, do you really want to move from `x`?
I really do want to move from x. I have thousands of lines doing that, though I expect a significant reduction when we start using coroutines.
My question is why do you want to move there instead of decay-copying?
I have an object that is expensive to copy so I move it from continuation to contination.
future<foo> f(expensive x) {
return foo1().then([x = std::move(x)] () mutable {
return foo2().then([x = std::move(x)] () mutable {
// do whatever with x
});
});
}
Or do you want to do a decay-copy into `x`? Because the latter is what `std::thread/async` do. And we already have a proposal for shortening that.
This is wonderful. How about, in addition, a unary &&? &&x == std::move(x). std::move(x) is so common it deserves some syntax.
`std::move(x)` is sufficiently short that it doesn't need to be given special syntax. It's 9 characters. By contrast, "std::forward<decltype(x)>(x)` is 24 characters, not counting the size of the `x` variable.
It's 9 characters for a very common operation.
It's not uncommon to see calls to std::move() dominate an expression visually, while contributing little semantically.
I have a strong dislike for the idea that `[x = std::move(x)]` should in any way be different from `[&&x]`. There needs to be a really good reason for doing that.
On Wednesday, August 16, 2017 at 3:44:20 PM UTC-4, Avi Kivity wrote:On 08/16/2017 06:08 PM, Nicol Bolas wrote:
On Wednesday, August 16, 2017 at 4:47:32 AM UTC-4, Avi Kivity wrote:On 08/15/2017 10:03 PM, Nicol Bolas wrote:
Also, do you really want to move from `x`?
I really do want to move from x. I have thousands of lines doing that, though I expect a significant reduction when we start using coroutines.
My question is why do you want to move there instead of decay-copying?
I have an object that is expensive to copy so I move it from continuation to contination.
future<foo> f(expensive x) {
return foo1().then([x = std::move(x)] () mutable {
return foo2().then([x = std::move(x)] () mutable {
// do whatever with x
});
});
}
Can you give me an example that wouldn't be far more readable if `co_await` got into C++20? I'm not a big fan of `co_await`, but it's basically going to make your "mutable move capture" feature obsolete.
Or do you want to do a decay-copy into `x`? Because the latter is what `std::thread/async` do. And we already have a proposal for shortening that.
This is wonderful. How about, in addition, a unary &&? &&x == std::move(x). std::move(x) is so common it deserves some syntax.
`std::move(x)` is sufficiently short that it doesn't need to be given special syntax. It's 9 characters. By contrast, "std::forward<decltype(x)>(x)` is 24 characters, not counting the size of the `x` variable.
It's 9 characters for a very common operation.
"very common operation" is based entirely on perspective. I don't do continuation-style programming, so what you call "very common" is very much not to me. Yes, a lot of people do this. But a lot of people don't. And this feature will affect all of them, by forcing them to learn that lambdas can sometimes have mutable captures without having the `mutable` keyword in them.
It's not uncommon to see calls to std::move() dominate an expression visually, while contributing little semantically.
"Dominate an expression visually"? Sure. But what you're talking about won't change that, since you're only talking about the case of lambda captures. Most expressions where `std::move` dominates the expression are variable declarations.
And I strongly disagree with the "contributing little semantically" suggestion. It's very important in many cases to know when an object has been moved from. Indeed, this was deliberately changed; earlier versions of rvalue references were very happy to move things without having to use `std::move`. That was changed because it make it too easy to break things by accident.
And broadly speaking, continuation lambdas tend to not be on the short side, so making their capture lists shorter isn't that important for readability. By contrast, lambdas that do forwarding do tend to be quite short. So making them shorter gives you more of an advantage.
std::plus<>()(x, y) is also short, but it's not something you want to use when doing math. Nor would you want std::addressof() to take the address of a variable.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ed2126d1-debe-43fe-b8bc-35c1a884cc5e%40isocpp.org.
I have a strong dislike for the idea that `[x = std::move(x)]` should in any way be different from `[&&x]`. There needs to be a really good reason for doing that.
The reason is, by making the lambda the owner of the contents, you're no longer protecting the original object from missing a mutation.
By forcing the user to make the entire lambda mutable, you're reducing the ability to detect this kind of bug on variables that _are_ copied by value.
I have a strong dislike for the idea that `[x = std::move(x)]` should in any way be different from `[&&x]`. There needs to be a really good reason for doing that.
The reason is, by making the lambda the owner of the contents, you're no longer protecting the original object from missing a mutation.
But both of them are making the lambda "the owner of the contents". My question is why should one way of transferring ownership be different from the other?
By forcing the user to make the entire lambda mutable, you're reducing the ability to detect this kind of bug on variables that _are_ copied by value.
I don't know what you mean by that. Your original declaration was that the use of `&&x` in a capture list causes the lambda to become `mutable`:
> I would like to re-raise [&&x] as a shortcut for [x = std::move(x)] () mutable.
Are you now saying that you want it to be limited to just `x` being `mutable`? Wouldn't it make more sense to just allow you to do this `[mutable &&x]`, `[mutable x = std::move(x)]` and so forth?
Why should mutability be tied to how the capture variable was initialized? These are entirely orthogonal constructs, so they should be handled with orthogonal syntax.