Recap: People keep asking for the proposed “no-auto” generic lambda parameter syntax like
[](e){ use(e); }
but EWG has a sustained objection to this on the grounds that, when we get constraints, we want people to use constrained lambdas, and without “auto” it was feared there was a syntactic carrot to prefer the unconstrained “(e)” form for its simplicity over “(Constraint e)” style.
I just saw an interesting point in my blog comments from user “Leo”:
Herb, if Bjarne’s expectations for C++14 (https://www.informit.com/articles/article.aspx?p=2080042) are fulfilled wouldn’t the STL provide an overload like the following (if it would work):
template<Container Cont, Predicate Pred>
requires Input_iterator<Iterator<Cont>>()
Iterator<Cont> find_if(Cont& c, Pred p);
If then the fact could be taken into account that a Predicate is not supposed to change its argument, […] one could write:
const auto i = find_if(emps, [&](e) { return e.name() == name; });
I removed with […] a different suggestion for what this should mean, but the overall suggestion that Predicate be used to deduce something about the lambda made me think:
Instead of having “(e)” deduce any variation of “auto” here (which EWG feels is undesirable), what if the “(e)” form only kicked into deduce a constraint? For example, the lambda would only be usable when passed to a constrained parameter in a context like this, and perhaps similar non-parameter situations like “Predicate p = [](e){ … };”?
In fact, if the user had to repeat something like “Predicate” in the lambda, wouldn’t that be redundant, adding another reason to not require it be repeated? And it seems it would actually be difficult for the user to constrain the lambda that way with current proposed syntax at the parameter site, because he wouldn’t write “(Predicate e)” since Predicate applies to the whole signature, not just the lambda.
More would have to be done to determine feasibility, but could something in this direction perhaps address the two concerns by allowing the “(e)” nicer syntax everyone wants, while also addressing the EWG concern because not only would it not be an unconstrained lambda, but:
- it would encourage calling constrained algorithms because you get a nicer syntax for calling constrained algorithms, and
- it would encourage writing constrained algorithms because they’d be easier to use with the nice syntax.
Thoughts?
Herb
I still don't like that syntax. I think of it as irregular and out of character with the rest of the language.
New names are introduced by a type. Names can be optional. This would no longer be true.
Recap: People keep asking for the proposed “no-auto” generic lambda parameter syntax like
[](e){ use(e); }
but EWG has a sustained objection to this on the grounds that, when we get constraints, we want people to use constrained lambdas, and without “auto” it was feared there was a syntactic carrot to prefer the unconstrained “(e)” form for its simplicity over “(Constraint e)” style.
I just saw an interesting point in my blog comments from user “Leo”:
Herb, if Bjarne’s expectations for C++14 (https://www.informit.com/articles/article.aspx?p=2080042) are fulfilled wouldn’t the STL provide an overload like the following (if it would work):
template<Container Cont, Predicate Pred>
requires Input_iterator<Iterator<Cont>>()
Iterator<Cont> find_if(Cont& c, Pred p);
If then the fact could be taken into account that a Predicate is not supposed to change its argument, […] one could write:
const auto i = find_if(emps, [&](e) { return e.name() == name; });
I removed with […] a different suggestion for what this should mean, but the overall suggestion that Predicate be used to deduce something about the lambda made me think:
Instead of having “(e)” deduce any variation of “auto” here (which EWG feels is undesirable), what if the “(e)” form only kicked into deduce a constraint? For example, the lambda would only be usable when passed to a constrained parameter in a context like this, and perhaps similar non-parameter situations like “Predicate p = [](e){ … };”?
In fact, if the user had to repeat something like “Predicate” in the lambda, wouldn’t that be redundant, adding another reason to not require it be repeated? And it seems it would actually be difficult for the user to constrain the lambda that way with current proposed syntax at the parameter site, because he wouldn’t write “(Predicate e)” since Predicate applies to the whole signature, not just the lambda.
More would have to be done to determine feasibility, but could something in this direction perhaps address the two concerns by allowing the “(e)” nicer syntax everyone wants, while also addressing the EWG concern because not only would it not be an unconstrained lambda, but:
- it would encourage calling constrained algorithms because you get a nicer syntax for calling constrained algorithms, and
- it would encourage writing constrained algorithms because they’d be easier to use with the nice syntax.
Thoughts?
Herb
> template<Container Cont, Predicate Pred>The constraints aren't correct, here. Predicate requires the user to
> requires Input_iterator<Iterator<Cont>>()
> Iterator<Cont> find_if(Cont& c, Pred p);
specify the argument types in addition to the function type.
Predicate<Pred, Value_type<C>>
would be correct.
Same problem here.
> “Predicate p = [](e){ … };”?
> Thoughts?
I think the proposal is a little confused about the problem it is
trying to solve. You shouldn't need to compare the lambda syntax
against the call interface required be a constraint to know that
you're writing a lambda expression.
// Snippets from Andrew's 'origin' library
// https://github.com/asutton/origin/blob/master/origin/range/algorithm.hpp
template<Input_range R, typename F>requires Function<F, Value_type<R>>()inline F for_each(R&& range, F fn){using std::begin;using std::end;return std::for_each(begin(range), end(range), fn);}
template<typename F, typename... Args>concept bool Function(){return Copy_constructible<F>()&& requires (F f, Args... args) {f(args...);};}// -------------------End Origin Code----------------------------------//// below when i say "here", i mean at point of call of for_each// i.e. when for_each's constraints are checked.
template<Range R> void print(R range) {using Element_type = Value_type<R>;for_each(range, [](Element_type el) {cout << el;}); // should not error here, might at instantiation of lambda
for_each(range, [](auto element_is_a_range) {cout << element_is_a_range;}); // should not error here, might at instantiation of lambda
for_each(range, [](auto *el) {cout << *el;
}); // might error here, upon instantiation of print with R = vector<int>,// because:
// !Function<decltype(lambda), int /*Value_type<R>*/>()
// with an error message that T* can not deduce to an int
// Assuming we allow constraints as lambda parameters,
// this would be preferred:for_each(range, [](Range element_is_a_range) {cout << element_is_a_range;}); // might error here, upon instantiation of print with R = vector<int>,// because:
// !Function<decltype(lambda), int /*Value_type<R>*/>()
// with an error message that int is not a Range
// So what does [](element_is_a_range) { ... } syntax
// buy us, since we need to explicitly state which constraint// it is that we are placing on the parameter to get a useful error
// message at the point of call?
// What does it mean to deduce a constraint on a lambda
// parameter from the context it is being used in, and how// is that ever useful?
}int main() {std::vector<int> vi{1, 2, 3};print(vi);}
On 21 May 2013 05:59, Bjarne Stroustrup <b...@cs.tamu.edu> wrote:
I still don't like that syntax. I think of it as irregular and out of character with the rest of the language.
I don't like it because I think it's unreadable. Easy to write, but not easy to read, when maintaining
code.
New names are introduced by a type. Names can be optional. This would no longer be true.
I don't think that's the case for lambda captures, so we're already not quite regular in that sense.
--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+u...@isocpp.org.
To post to this group, send email to conc...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/concepts/?hl=en-US.
On 5/20/2013 11:47 PM, Ville Voutilainen wrote:
On 21 May 2013 05:59, Bjarne Stroustrup <b...@cs.tamu.edu> wrote:
I still don't like that syntax. I think of it as irregular and out of character with the rest of the language.
I don't like it because I think it's unreadable. Easy to write, but not easy to read, when maintaining
code.
I forgot to mention the aesthetic aspect. Yes, people clamor for minimal syntax. Yes, people clamor for what they see in other languages. However, I find the absence of a "type" when introducing a name aesthetically grating, and as Ville point out better for writing than for reading.
Aesthetics is to a large extent a subjective measure, but regularity/consistency is not.
New names are introduced by a type. Names can be optional. This would no longer be true.
I don't think that's the case for lambda captures, so we're already not quite regular in that sense.
It ought to be regular. I see no reason why we would never have to write a lambda that did not use an argument required by the caller.
Recap: People keep asking for the proposed “no-auto” generic lambda parameter syntax like
[](e){ use(e); }
| He was talking about the lambda capture list, not the lambda's parameter list:
|
| [no_type, still_no_type](auto needs_a_type, auto inconsistent) {}
|
| Like that. The variables captured introduce a name that is distinct from the
| actual local variables they capture from. But they don't use a type name.
I fail to see the inconsistency. The capture list enumerates names that
need to be captured and which must have already been introduced by a
declaration, they don't introduce new declarations in the scope of the lambda.
What I am strongly against is arguments/parameters without a type (orsyntactic placeholder for a type).
His point is that your original statement was "New names are introduced by a type." Yet Lambdas introduce capture names, which are most certainly new names, without a type. The point being that lambdas already violate this principle in one respect, so why not one more?
> Does [](e) { .... } mean a unary-lambda with a nameless parameter of type e?
First, function parameter lists allow nameless parameters. Aside from the other considerations, do we really think that the ability to not name parameters, which IIRC was inherited from C and is only useful rarely for shutting up compiler warnings about unused parameters, is a desirable and compelling feature? Personally, I don't think it is compelling, and it's a quirk/wart to teach that adds complexity to the existing language, so I wouldn't mind gradually moving away from it.
Second, and more persuasive (even though I'm not persuaded) he argues that objects should have types (even if placeholders). That's a stronger argument IMO, though I still find [](e) attractive and note that the placeholders don't often add information today (e.g., auto, T). I suppose this is an aesthetic issue.
On 5/21/2013 3:54 PM, Herb Sutter wrote:No way! Unnamed arguments have been in C++ since the earliest days and are widely used.
Does [](e) { .... } mean a unary-lambda with a nameless parameter of type e?The proposal proposed "no." :)
Note that there are two related parts to Bjarne's argument:
First, function parameter lists allow nameless parameters. Aside from the other considerations, do we really think that the ability to not name parameters, which IIRC was inherited from C and is only useful rarely for shutting up compiler warnings about unused parameters, is a desirable and compelling feature? Personally, I don't think it is compelling, and it's a quirk/wart to teach that adds complexity to the existing language, so I wouldn't mind gradually moving away from it.
I understand Bjarne's concern about consistency and not creating gratuitous differences between function parameter lists and lambda parameter lists. I agree with that consistency principle. But besides supporting nameless parameters in both contexts, the other way to be consistent is to move away from nameless parameters in both contexts.
The "placeholders" do not add information (beyond introducing a new name) today, but real types do. Consider:
Second, and more persuasive (even though I'm not persuaded) he argues that objects should have types (even if placeholders). That's a stronger argument IMO, though I still find [](e) attractive and note that the placeholders don't often add information today (e.g., auto, T). I suppose this is an aesthetic issue.
sort(vs,[](const string& x, const string& y) { return x>y; }); // assume a container algorithm for a vector<string>
sort(vs,[](auto x, auto y) { return x>y; });
In the second example two (undesirable) things happened:
(1) the strings were passes by value
(2) checking of the > was postponed to the point of call within sort()
The first problem can be addressed by adding &:
sort(vs,[](auto& x, auto& y) { return x>y; });
Without the auto to which to attach the &, we would have to invent a new syntax (for a "freestanding" &). Also, we now forgot to specify that the lambda don't modify its arguments (a serious problem for static analysis), so we get:
sort(vs,[](const auto& x, const auto& y) { return x>y; });
Woop-dee-dooh! Generic lambdas saved us two characters :-)
The second problem is addressed by "concepts lite": We can apply the constraints from the template parameter to the lambda that is the template argument.
But what if the sort() isn't constrained? (for years there will be unconstrained templates floating around). Well, in the general case, we would apply a concept (using the "terse notation"):
sort(vs,[](const String_like& x, const String_like& y) { return x>y; });
Though, I suspect that in many cases, naming the real type will be simpler.
Herb
--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+unsubscribe@isocpp.org.
Herb Sutter <hsu...@microsoft.com> writes:
| > Does [](e) { .... } mean a unary-lambda with a nameless parameter of type e?
|
| The proposal proposed "no." :)
I was obliquely pointing at a parsing ambiguity the proposal
didn't address.
The "placeholders" do not add information (beyond introducing a new name) today, but real types do. Consider:
sort(vs,[](const string& x, const string& y) { return x>y; }); // assume a container algorithm for a vector<string>
sort(vs,[](auto x, auto y) { return x>y; });
In the second example two (undesirable) things happened:
(1) the strings were passes by value
(2) checking of the > was postponed to the point of call within sort()
The first problem can be addressed by adding &:
sort(vs,[](auto& x, auto& y) { return x>y; });
Without the auto to which to attach the &, we would have to invent a new syntax (for a "freestanding" &). Also, we now forgot to specify that the lambda don't modify its arguments (a serious problem for static analysis), so we get:
sort(vs,[](const auto& x, const auto& y) { return x>y; });
Woop-dee-dooh! Generic lambdas saved us two characters :-)
The second problem is addressed by "concepts lite": We can apply the constraints from the template parameter to the lambda that is the template argument.
But what if the sort() isn't constrained? (for years there will be unconstrained templates floating around). Well, in the general case, we would apply a concept (using the "terse notation"):
sort(vs,[](const String_like& x, const String_like& y) { return x>y; });
Though, I suspect that in many cases, naming the real type will be simpler.
On Tue, May 21, 2013 at 6:33 PM, Bjarne Stroustrup <b...@cs.tamu.edu> wrote:
<snip>
The "placeholders" do not add information (beyond introducing a new name) today, but real types do. Consider:
sort(vs,[](const string& x, const string& y) { return x>y; }); // assume a container algorithm for a vector<string>
sort(vs,[](auto x, auto y) { return x>y; });
In the second example two (undesirable) things happened:
(1) the strings were passes by value
(2) checking of the > was postponed to the point of call within sort()
The first problem can be addressed by adding &:
sort(vs,[](auto& x, auto& y) { return x>y; });
Without the auto to which to attach the &, we would have to invent a new syntax (for a "freestanding" &). Also, we now forgot to specify that the lambda don't modify its arguments (a serious problem for static analysis), so we get:
sort(vs,[](const auto& x, const auto& y) { return x>y; });
Woop-dee-dooh! Generic lambdas saved us two characters :-)
OK.
The second problem is addressed by "concepts lite": We can apply the constraints from the template parameter to the lambda that is the template argument.
Just to clarify, when you say apply the constraints from the template parameter to the lambda that is the template argument, do you mean that if sort is declared as following:
template<Range R, class Rel>
requires Relation<Rel, Value_type<R>>() void sort(R& range, Rel pred);
then at the point of call of sort (assuming the correct overload/specialization is selected), Relation<decltype(lambda), Value_type<R>> will be checked?
But when would
that ever emit a useful error for the above generic lambda at the point of call of sort (i guess if the lambda's result can not be converted to bool)? (I imagine instantiation of the generic lambda might emit an error if '>' does not apply).
But what if the sort() isn't constrained? (for years there will be unconstrained templates floating around). Well, in the general case, we would apply a concept (using the "terse notation"):
sort(vs,[](const String_like& x, const String_like& y) { return x>y; });
Like almost everyone else, I too feel that supporting the "terse notation" where we replace the name of the type with a concept is highly appealing, and assuming you get the constraint of the generic parameter right (both for the template the lambda is passed to and for the way the parameter is used within the body of the lambda), the error message at the point of call of sort would be a welcome diagnostic.
(Although I imagine that some people will find it annoyingly inconsistent if this syntax does not work for function templates - but something is better than nothing).
It is my hope that we support the truly ultra-terse syntax for lambdas (along the lines that Herb proposes, i.e. not just terseness of constraints within lambdas, but also terseness as it applies to the other syntactic components of the lambda) without compromising readability and clarity (for some definition of readable and clear ;)
Though, I suspect that in many cases, naming the real type will be simpler.
or using auto ;)
I am not sure what Nevin has in mind presently, but I believe he had once posted favoring the use of '+' instead of an auto (I briefly alluded to this potential direction, as inspired by Nevin, on pg 3 of N3559).