A rough idea: Constraints and lambdas and "[](e){ ... }" syntax

356 views
Skip to first unread message

Herb Sutter

unread,
May 20, 2013, 2:12:56 PM5/20/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)

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

 

 

 

 

Bjarne Stroustrup

unread,
May 20, 2013, 10:59:37 PM5/20/13
to Herb Sutter, conc...@isocpp.org, fai...@gmail.com
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.
Lambdas and templates share a large part of their syntax. lambdas and functions share a large part of their syntax. This would no longer be true.

If a lambda can have a unused (and therefore unnamed) argument, we have a hard ambiguity. If not, why can't a lambda have an unnamed argument?

Ville Voutilainen

unread,
May 21, 2013, 12:47:47 AM5/21/13
to conc...@isocpp.org, Herb Sutter, fai...@gmail.com
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.
 


Andrew Sutton

unread,
May 21, 2013, 7:45:34 AM5/21/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)
> template<Container Cont, Predicate Pred>
> requires Input_iterator<Iterator<Cont>>()
> Iterator<Cont> find_if(Cont& c, Pred p);

The constraints aren't correct, here. Predicate requires the user to
specify the argument types in addition to the function type.

Predicate<Pred, Value_type<C>>

would be correct.

> “Predicate p = [](e){ … };”?

Same problem here.

> 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. Involving constraints seems like a
long way to go to get terser syntax.

The idea of replacing auto with constraint names appeals to me, but
not for this reason.

Andrew

Jason McKesson

unread,
May 21, 2013, 8:08:50 AM5/21/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu), hsu...@microsoft.com
On Monday, May 20, 2013 11:12:56 AM UTC-7, Herb Sutter wrote:

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 thought the reason we didn't do this was for the sake of the ambiguity: not knowing whether `e` is intended to be a typename or a parameter name. Though obviously we can declare a heuristic (if `e` is a known typename, then it's a typename, otherwise it's a parameter name), but visually it's ambiguous. (note: personally, I don't agree with this argument. Well, I agree that it's visually ambiguous; I just don't care. I'm willing to accept the visual ambiguity in order to have the shorter syntax and thus not have to type `auto` everywhere. Other strongly typed languages get away with it for their lambdas, why not C++?)

I don't see how the "syntactic carrot" reasoning makes sense; if someone doesn't want to constrain their lambdas, then they won't. `auto` is going to be shorter than virtually every concept name, so even the most basic constraints, without any template parameters, will be longer than `auto`.

So people are still going to use unconstrained lambdas more than they need to.
 

 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

These kinds of context-sensitive solutions always make me nervous. The idea of functionally changing the meaning of a C++ expression based primarily on the way the expression is used, rather than the nature of the expression itself. Braced-init-lists work (to the extent that they do work) because they're explicitly not expressions; they're just syntactical containers of stuff. To have the definition of a type change based on where it is first used?

I don't feel that this is a good idea.

I understand the reasoning: we want lambdas to be constrained based on where you use them. But at the same time, we may just have to accept some verbosity here.

Faisal Vali

unread,
May 21, 2013, 9:58:22 AM5/21/13
to Andrew Sutton, Herb Sutter, conc...@isocpp.org
On Tue, May 21, 2013 at 6:45 AM, Andrew Sutton <andrew....@gmail.com> wrote:
> template<Container Cont, Predicate Pred>
>     requires Input_iterator<Iterator<Cont>>()
> Iterator<Cont> find_if(Cont& c, Pred p);

The constraints aren't correct, here. Predicate requires the user to
specify the argument types in addition to the function type.

  Predicate<Pred, Value_type<C>>

would be correct.

> “Predicate p = [](e){ … };”?

Same problem here.

> 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.

Yes.  I was a little confused by the proposal too.  It would help me if we included a little more detail and some code to
get a better sense of what it is we are looking for:

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);
}

Please see the final comment in the example above to get a sense of my confusion -
I apologize for the clutter, if I am missing something obvious.

Thanks!

Bjarne Stroustrup

unread,
May 21, 2013, 11:39:45 AM5/21/13
to conc...@isocpp.org, Ville Voutilainen, Herb Sutter, fai...@gmail.com
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.

 


--
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.
 
 

Jason McKesson

unread,
May 21, 2013, 12:16:13 PM5/21/13
to conc...@isocpp.org, Ville Voutilainen, Herb Sutter, fai...@gmail.com, b...@cs.tamu.edu
On Tuesday, May 21, 2013 8:39:45 AM UTC-7, Bjarne Stroustrup wrote:
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.

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.

J. Daniel Garcia

unread,
May 21, 2013, 1:49:17 PM5/21/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)


On Mon, May 20, 2013 at 8:12 PM, Herb Sutter <hsu...@microsoft.com> wrote:

Recap: People keep asking for the proposed “no-auto” generic lambda parameter syntax like

 

[](e){ use(e); }

 



If I recall correctly, the first time we say we discussed polimorphic/generic lambdas was in Portland. The discussion was then not related to concepts and I remember a strong support of having "compulsory auto" for this case.

Personally, I think it is difficult to read and it may lead to maintainability problems.

We could probably end-up with coding standards and enforcing tools requiring to avoid such constructs.
--
J. Daniel Garcia

Bjarne Stroustrup

unread,
May 21, 2013, 1:55:35 PM5/21/13
to Gabriel Dos Reis, conc...@isocpp.org, Ville Voutilainen, Herb Sutter, fai...@gmail.com
On 5/21/2013 12:20 PM, Gabriel Dos Reis wrote:
> 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.
>
> -- Gaby


Maybe I'm confused. The example was [](e){ use(e); } . That looks like
an argument e rather than a binding to a local variable e.

What I am strongly against is arguments/parameters without a type (or
syntactic placeholder for a type).

Ville Voutilainen

unread,
May 21, 2013, 2:04:22 PM5/21/13
to Gabriel Dos Reis, conc...@isocpp.org, Herb Sutter, fai...@gmail.com, Bjarne Stroustrup
On 21 May 2013 20:20, Gabriel Dos Reis <g...@axiomatics.org> wrote:
| 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.



[x=5, y=6]() {...}

that one doesn't really capture anything that has been already introduced. The init-captures
in http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3648.html make that valid.
And there's no placeholder for a constraint in it.

Jason McKesson

unread,
May 21, 2013, 3:35:50 PM5/21/13
to conc...@isocpp.org, Gabriel Dos Reis, Ville Voutilainen, Herb Sutter, fai...@gmail.com, b...@cs.tamu.edu

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?

You've now amended the statement to say that it's parameter names that need a type.

Ville Voutilainen

unread,
May 21, 2013, 3:40:52 PM5/21/13
to Jason McKesson, conc...@isocpp.org, Gabriel Dos Reis, Herb Sutter, fai...@gmail.com, Bjarne Stroustrup
On 21 May 2013 22:35, Jason McKesson <jmck...@gmail.com> wrote:
What I am strongly against is arguments/parameters without a type (or
syntactic 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?

Well, rest assured that it has never been my point that since init-captures introduce names without using
a type, it would be any kind of an open hunting license to continue with that path.

Regarding Gaby's note about that being a defect, the problem is that the legacy captures don't have
a type, so supporting both typeless and typed captures becomes interesting parsing-wise. Then
again, I can always have something like

template <Constraint C> auto constrain(C&& c) { return forward<C>(c); }

and then do

[x = constrain(whatever)](){}

and I can still constrain the captures.

Gabriel Dos Reis

unread,
May 21, 2013, 3:30:56 PM5/21/13
to Ville Voutilainen, conc...@isocpp.org, Herb Sutter, fai...@gmail.com, Bjarne Stroustrup
Ah, I haven't had time to digest all the new novelties from the Bristol
meeting. I think we can call this a design defect :-)

-- Gaby

Gabriel Dos Reis

unread,
May 21, 2013, 3:25:32 PM5/21/13
to Bjarne Stroustrup, conc...@isocpp.org, Ville Voutilainen, Herb Sutter, fai...@gmail.com
That is correct. But, he was talking about caputure list.

| What I am strongly against is arguments/parameters without a type (or
| syntactic placeholder for a type).

We are in violent agreement.

-- Gaby

Gabriel Dos Reis

unread,
May 20, 2013, 7:14:02 PM5/20/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)
Herb Sutter <hsu...@microsoft.com> writes:

| Recap: People keep asking for the proposed ?no-auto? generic lambda parameter
| syntax like

[...]

| Thoughts?

There is something to be said for simplicity :-)

I would probably repeat myself, but if he hadn't insisted that the body
of a lambda must have the right to be as complex as the body of any
function we can possibly write, then I suspect both the syntax and the
rules surrounding lambdas might have been simpler.

That said, if you are in constrained context, you can use the available
information to conduct a local bidirectional type checking which allows
you to leave out type annotation in certain decidable cases (but I'm
sure someone will point out a SFINAE case where it doesn't'; that is
beside the point.) The real issue I see is that C++ declarations have a
vexing tendency of displaying ambiguity with expressions; there is a
grammar ambiguity to start with. Does [](e) { .... } mean a
unary-lambda with a nameless parameter of type e?

-- Gaby

Gabriel Dos Reis

unread,
May 21, 2013, 1:20:29 PM5/21/13
to conc...@isocpp.org, Ville Voutilainen, Herb Sutter, fai...@gmail.com, b...@cs.tamu.edu

Gabriel Dos Reis

unread,
May 21, 2013, 4:33:44 PM5/21/13
to Ville Voutilainen, Jason McKesson, conc...@isocpp.org, Herb Sutter, fai...@gmail.com, Bjarne Stroustrup
Ville Voutilainen <ville.vo...@gmail.com> writes:

[...]

| Regarding Gaby's note about that being a defect, the problem is that the legacy
| captures don't have
| a type, so supporting both typeless and typed captures becomes interesting
| parsing-wise.

Right. Interesting parsing problem :-)

| Then again, I can always have something like
|
| template <Constraint C> auto constrain(C&& c) { return forward<C>(c); }
|
| and then do
|
| [x = constrain(whatever)](){}
|
| and I can still constrain the captures.

yeah.

-- Gaby

Herb Sutter

unread,
May 21, 2013, 4:54:18 PM5/21/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)
> 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.

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.

Herb


Nevin Liber

unread,
May 21, 2013, 5:10:04 PM5/21/13
to conc...@isocpp.org
On 21 May 2013 15:54, Herb Sutter <hsu...@microsoft.com> wrote:
> 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.

To be replaced with what?  Is this really worth breaking lots of existing code (if you do it for all function declarations)?

I've found unused parameters to be a lot more prevalent in C++ than in C, due to code having to match interfaces.  I still want warnings for unintentionally unused parameters.
 
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.

T adds information.  For instance:

struct Foo
{
    // roughly equivalent to a polymorphic lambda
    template<typename A, typename B>
    auto operator()(A, B) const { /* ... */ }
};

This is not the same as:

struct Foo
{
    // not equivalent to a polymorphic lambda
    template<typename T>
    auto operator()(T, T) const { /*... */ }
};

While I do want a terser syntax, I do not want the proposed syntax.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Bjarne Stroustrup

unread,
May 21, 2013, 7:33:35 PM5/21/13
to Herb Sutter, conc...@isocpp.org, fai...@gmail.com
On 5/21/2013 3:54 PM, Herb Sutter wrote:
>> 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.

No way! Unnamed arguments have been in C++ since the earliest days and
are widely used.

>
> 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.

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.

>
> Herb
>
>

Herb Sutter

unread,
May 21, 2013, 7:47:38 PM5/21/13
to conc...@isocpp.org, fai...@gmail.com
Bjarne wrote:
> No way! Unnamed arguments have been in C++ since the earliest days and
> are widely used.

Okay, it's probably unrealistic to jettison them anyway for compatibility reasons.

Nevin, would you elaborate on the terse syntax you did like?

Herb



Richard Smith

unread,
May 21, 2013, 7:58:47 PM5/21/13
to conc...@isocpp.org, Herb Sutter, fai...@gmail.com
On Tue, May 21, 2013 at 4:33 PM, Bjarne Stroustrup <b...@cs.tamu.edu> wrote:
On 5/21/2013 3:54 PM, Herb Sutter wrote:
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.

No way! Unnamed arguments have been in C++ since the earliest days and are widely used.



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.

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 :-)

I would expect that the 'perfect forwarding' idiom would become prevalent here. This avoids the verbosity and the copy:

sort(vs, [](auto &&x, auto &&y) { return x > y; });
 
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.

Faisal Vali

unread,
May 21, 2013, 10:07:35 PM5/21/13
to Gabriel Dos Reis, conc...@isocpp.org, Bjarne Stroustrup (bs@cs.tamu.edu)



On Tue, May 21, 2013 at 4:49 PM, Gabriel Dos Reis <g...@axiomatics.org> wrote:
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.


I tried to acknowledge this ambiguity in N3559 Appendix B.


Faisal Vali

unread,
May 21, 2013, 10:11:14 PM5/21/13
to Herb Sutter, conc...@isocpp.org
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).
 

Faisal Vali

unread,
May 21, 2013, 10:55:19 PM5/21/13
to Bjarne Stroustrup, Herb Sutter, conc...@isocpp.org

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 ;)

Bjarne Stroustrup

unread,
May 21, 2013, 11:12:00 PM5/21/13
to fai...@gmail.com, Herb Sutter, conc...@isocpp.org
On 5/21/2013 9:55 PM, Faisal Vali wrote:

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? 
Yes


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). 

At the point of call, R is known. This we can check the body of the lambda exactly as if we had been in a non-generic context. We do not need to delay error detection until instantiation of sort.



 
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.

Yes. And it both the generic function and the generic lambda are constrained the check is simply against the intersection of their constraints.


(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).

What I proposes and prefers is uniform for lambdas and templates. That's a key design aim.


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 ;)

I think that the absolutely minimal notation (suggested by Herb and others) would have very serious bad effects - as outlined above.

 
Though, I suspect that in many cases, naming the real type will be simpler.


or using auto ;)

No. Auto would postpone checking and "plain auto" would impose call by value.

Faisal Vali

unread,
May 21, 2013, 11:32:23 PM5/21/13
to Bjarne Stroustrup, Herb Sutter, conc...@isocpp.org
If the generic lambda does not have a trailing return type, the generic lambda would be required to be instantiated to deduce its return type while checking the constraint, and an error would be emitted at that time (a non SFINAE error during overload resolution/constraint checking).

But if i understand you correctly, if the generic lambda did have a trailing return type, you would still expect the implementation to instantiate the lambda right there - but only right after overload resolution and constraint checking has selected the most constrained overload (and not wait till the end of the translation unit to instantiate the body, like some implementations)?



Faisal Vali

unread,
May 21, 2013, 11:53:00 PM5/21/13
to Bjarne Stroustrup, Herb Sutter, conc...@isocpp.org
Sorry for being dense.  I think I do see your point here.  Without constraints, if sort was defined as such:
template<class Container, class Relation>
void sort(Container &C, Relation &R);
and we passed in a generic lambda, the *only* point at which we could specialize the lambda and instantiate its body is within the body of sort when it is being instantiated.

[Unless of course we do something like the following (which would only work for non-capturing lambdas):
template<class Container> void sort2(Container& C, bool (*comp)(const Value_type<Container>&, const Value_type<Container>&));

sort2(vs, [](auto e1, auto e2) { return e1.bad(e2); }); //error during return type deduction 
sort2(vs, [](auto e1, auto e2) -> bool { return e1.bad(e2); });  // error during instantiation
]

With constraints, the implementation has enough context to emit the diagnostic at the point of call of sort (via the constraint checking mechanism) - which is not possible for the general case without constraints.

Thanks!
 




Andrew Sutton

unread,
May 22, 2013, 7:31:47 AM5/22/13
to conc...@isocpp.org, Bjarne Stroustrup, Herb Sutter
> Sorry for being dense. I think I do see your point here. Without
> constraints, if sort was defined as such:
> template<class Container, class Relation>
> void sort(Container &C, Relation &R);
> and we passed in a generic lambda, the *only* point at which we could
> specialize the lambda and instantiate its body is within the body of sort
> when it is being instantiated.

Without constraints, I think so.

With constraints the lambda (or part of it) has to be instantiated
during constraint checking in order to determine if it satisfies the
requirements of sort(). That happens before you get into the body of
sort.

> sort2(vs, [](auto e1, auto e2) { return e1.bad(e2); }); //error during
> return type deduction

I think this should becomes a regular constraint error. That is,
assuming return type deduction defines the result type of the generic
lambda as decltype(e1.bad(e2)), and does not wait until it is
instantiated to assign the expression a type.

If so (and I hope so), we can check result types in the usual way as
part of the constraint.

Andrew

Nevin Liber

unread,
May 22, 2013, 10:38:32 AM5/22/13
to conc...@isocpp.org
On 21 May 2013 21:11, Faisal Vali <fai...@gmail.com> wrote:


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).

+1

Jason Merrill

unread,
May 22, 2013, 10:58:11 AM5/22/13
to conc...@isocpp.org, Bjarne Stroustrup, Herb Sutter
On 05/22/2013 07:31 AM, Andrew Sutton wrote:
> I think this should becomes a regular constraint error. That is,
> assuming return type deduction defines the result type of the generic
> lambda as decltype(e1.bad(e2)), and does not wait until it is
> instantiated to assign the expression a type.

Return type deduction happens during instantiation, so it is not subject
to SFINAE.

Jason

Gabriel Dos Reis

unread,
May 21, 2013, 5:49:03 PM5/21/13
to conc...@isocpp.org, fai...@gmail.com, Bjarne Stroustrup (bs@cs.tamu.edu)
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.

| 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

That is not correct. C does not allow nameless parameters. It was one
of the early C++ inventions. Yes, it is very useful -- a primary
example is when you have to define callbacks defined by API.

The alternative to that C++ facility I've seen is for people to decorate
their code with ugly ATTRIBUTE_UNUSED, with ATTRIBUTE_UNUSED defined by
macro hackey. Lot of people compile their code with tha "unused
warning" on so that they can catch cheap mispelling errors, etc. Most
C++ compilers offer that as a standard warning.

Besides, I am unsure it is OK to ask people who don't particularly
desire to write type-less lambdas to give up something they use reguarly
in exchange of something they don't desire to have and won't use.

-- Gaby
Reply all
Reply to author
Forward
0 new messages