Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Can lambda expressions have const-qualifiers?

858 views
Skip to first unread message

Andy Venikov

unread,
Jan 17, 2013, 8:20:07 PM1/17/13
to
I was always under impression that lambdas could have cv-qualifiers.
At least that's how I interpreted 5.1.2[5]:

"An attribute-specifier-seq in a lambda-declarator appertains to the
type of the corresponding function call operator."

For example:

....
SomeExpensiveToCopyClass a;
...
[&]() const { //use const a here }

This way the compiler will notify me if I'm trying to modify a in a
lambda function.

Unfortunately, when compiled under gcc (4.7.2), it gives me an error
that const does not belong there.

Is my understanding not correct? Or is it just a gcc bug (i.e.
not-implemented feature)?


Thanks,
Andy.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Zhihao Yuan

unread,
Jan 18, 2013, 1:53:33 PM1/18/13
to
On Thursday, January 17, 2013 7:20:07 PM UTC-6, Andy Venikov wrote:
> "An attribute-specifier-seq in a lambda-declarator appertains to the
> type of the corresponding function call operator."

An attribute-specifier-seq is a new feature added to C++11 --
for example, [[ noreturn ]].

According to 5.1.2/1, syntactically, `mutable` is especially allowed
here, since the keyword was a storage specifier, not a cv-qualifier.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://4bsd.biz/

Daniel Krügler

unread,
Jan 18, 2013, 1:56:19 PM1/18/13
to
On 2013-01-18 02:20, Andy Venikov wrote:
> I was always under impression that lambdas could have cv-qualifiers.

No, the grammar doesn't support them to be added. The relevant part is:

lambda-declarator:
( parameter-declaration-clause ) mutable_opt
exception-specification_opt attribute-specifier-seq_opt
trailing-return-type_opt

Note that const qualifiers aren't possible, because the effective
function call operator has an const-qualifier unless you add the
"mutable" keyword as shown above because 5.1.2 p5 says:

"[..] This function call operator is declared const (9.3.1) if and only
if the lambda-expression’s parameter-declaration-clause is not followed
by mutable. It is neither virtual nor declared volatile. [..]"

Side note: The effect of the current rules is that you cannot add
volatile qualifiers to the lambda function call operator at all (neither
implicit nor explicit).

> At least that's how I interpreted 5.1.2[5]:
>
> "An attribute-specifier-seq in a lambda-declarator appertains to the
> type of the corresponding function call operator."

This interpretation is wrong, because cv-qualifiers are not part of
attribute-specifier-seq. The latter exist to add attributes, but
cv-qualifiers aren't attributes, they are a completely separate grammar
element.

> For example:
>
> ....
> SomeExpensiveToCopyClass a;
> ...
> [&]() const { //use const a here }

You don't need this, because const is implied.

> This way the compiler will notify me if I'm trying to modify a in a
> lambda function.

No, it wouldn't, even if it were allowed. Remember that capturing by
reference is intended to bind a reference or pointer to the
corresponding variable. 5.1.2 p17 says:

"Every id-expression that is an odr-use (3.2) of an entity captured by
copy is transformed into an access to the corresponding unnamed data
member of the closure type."

For reference capturing there does not exist a transformation into a
corresponding data member of the closure. Typically implementations will
use a pointer or reference to that entity, but this has the effect that
cv-qualifiers are considered "flat" here, similar to

struct Lambda {
SomeExpensiveToCopyClass* a;
void operator()() const { ... } // Modifying *a is OK
};

> Unfortunately, when compiled under gcc (4.7.2), it gives me an error
> that const does not belong there.

The compiler is correct, because cv-qualifiers aren't feasible in this
context.

> Is my understanding not correct?

No.

> Or is it just a gcc bug (i.e.
> not-implemented feature)?

No.

HTH & Greetings from Bremen,

Daniel Krügler

Andy Venikov

unread,
Jan 18, 2013, 5:27:21 PM1/18/13
to

On 01/18/2013 01:56 PM, Daniel Kr�gler wrote:<snip>

> For reference capturing there does not exist a transformation into
> a corresponding data member of the closure. Typically
> implementations will use a pointer or reference to that entity, but
> this has the effect that cv-qualifiers are considered "flat" here,
> similar to
>
> struct Lambda {
> SomeExpensiveToCopyClass* a;
> void operator()() const { ... } // Modifying *a is OK
> };
>
<snip>

I see, thanks!

Basically, there's no way to capture by const reference. So, when an
an object, that is expensive to copy, is captured by reference, but
not intended to be changed, the only option to the lambda writer is to
be diligent and not modify the referenced object. It's a pity though,
as we already have "mutable" there. We could qualify the lambda as
const, meaning that it's not going to modify any reference-captured
values.

Thanks,
Andy.

Daniel Krügler

unread,
Jan 19, 2013, 10:22:43 AM1/19/13
to

Am 18.01.2013 23:27, schrieb Andy Venikov:
>
> Basically, there's no way to capture by const reference. So, when an
> an object, that is expensive to copy, is captured by reference, but
> not intended to be changed, the only option to the lambda writer is
> to be diligent and not modify the referenced object.

Not really, you can also declare a reference to const T to the actual
object and capture only this reference, such as:

void foo() {
SomeExpensiveToCopyClass obj;
const SomeExpensiveToCopyClass& r = obj;
[&r]() { r.mutating(); }; // Error
}


> It's a pity though, as we already have "mutable" there.

The rationale is vice versa: Lambda closures were introduced to
simplify function object generation. Since most such function objects
have a const operator() overload, this was considered as the default.

The keyword mutable was introduced here to allow for lambda closures
with a non-const operator() overload. The current state is therefore a
consistent application of const-rules to lambda closures. It is
exactly the same if you would declare your own function object with a
pointer or reference member (because the pointer and reference won't
be modified).

> We could qualify the lambda as const, meaning that it's not going to
> modify any reference-captured values.

Theoretically this would be possible. I'm not so sure that this is
really a sufficient reason to extend the rules in that way, though.

HTH & Greetings from Bremen,

Daniel Kr�gler

Frank Birbacher

unread,
Jan 20, 2013, 10:16:49 PM1/20/13
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Am 19.01.13 16:22, schrieb Daniel Krügler:
> Theoretically this would be possible. I'm not so sure that this is
> really a sufficient reason to extend the rules in that way,
> though.

Sometimes I wish I could move-initialize the captured values,
especially when working with std::function like in:

typedef std::function<void(std::string const&)> Callback;

Callback twice(Callback c)
{
//will copy "c", but I want to move it into the lambda:
return [c](std::string const& s){
c(s); c(s); // call twice
};
}

The std::function does not do reference counting, does it? QOI?

Frank
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
Comment: GPGTools - http://gpgtools.org
Comment: keyserver x-hkp://pool.sks-keyservers.net

iEYEARECAAYFAlD8QN8ACgkQhAOUmAZhnmqARwCfSkC9/XCoEy+gPGZLj6Sp1wU4
P1UAnilqFzHoeMXZTMBE04Z5Q8Lzwgdy
=D1O5
-----END PGP SIGNATURE-----

Daniel Krügler

unread,
Jan 21, 2013, 3:40:18 AM1/21/13
to
On 2013-01-21 04:16, Frank Birbacher wrote:
> Am 19.01.13 16:22, schrieb Daniel Krügler:
>> Theoretically this would be possible. I'm not so sure that this is
>> really a sufficient reason to extend the rules in that way,
>> though.
>
> Sometimes I wish I could move-initialize the captured values,

Agreed. This is one of the major complaints currently (following the
request for polymorphic lambdas) in regard to constraints on lambda
expressions. To my knowledge there is work on this to solve the problem
but I cannot find a proposal at the very moment.

> especially when working with std::function like in:
>
> typedef std::function<void(std::string const&)> Callback;
>
> Callback twice(Callback c)
> {
> //will copy "c", but I want to move it into the lambda:
> return [c](std::string const& s){
> c(s); c(s); // call twice
> };
> }
>
> The std::function does not do reference counting, does it? QOI?

I'm not aware of one (but I haven't looked into many). There is some
evidence that a reference-counted implementation would not be
conforming, because the difference would be user-observable in a way
that would point to a conflict with the wording. In [func.wrap.func.con]
p4 we have for the copy constructor:

function(const function& f);
[..]

4 Postconditions: !*this if !f; otherwise, *this targets a copy of
f.target().

and [func.wrap.func.targ] p3 provides a means to observe the address of
the contained target:

template<typename T> T* target() noexcept;
template<typename T> const T* target() const noexcept;

[..]

3 Returns: If target_type() == typeid(T) a pointer to the stored
function target; otherwise a null pointer.

The wording for std::function is intended to support the
short-object-optimization, thought. There are several places that point
to special guarantees in regard to function pointers and
reference_wrapper objects, such as

"shall not throw exceptions if f’s target is a callable object passed
via reference_wrapper or a function pointer."

and especially the note

[ Note: Implementations are encouraged to avoid the use of dynamically
allocated memory for small callable objects, for example, where f’s
target is an object holding only a pointer or reference to an object and
a member function pointer. —end note ]

I just notice that there are currently no no-throw-guarantees for
targets that are pointer to members, this looks like an oversight to me
and should be changed.

HTH & Greetings from Bremen,

Daniel Krügler

MJanes

unread,
Jan 21, 2013, 3:00:35 PM1/21/13
to
On Monday, January 21, 2013 9:40:18 AM UTC+1, Daniel Krügler wrote:
> Agreed. This is one of the major complaints currently (following the
> request for polymorphic lambdas) in regard to constraints on lambda
> expressions. To my knowledge there is work on this to solve the problem

happy to hear this; speaking of the OP issue, it would be nice to
write also something like

[const &](/*...*/){/*...*/} and
[const &some_variable](/*...*/){/*...*/}

with the semantics suggested by the OP ( that is, a capture by const
reference ).

alternatively, it would be nice extending the capture-list syntax by
allowing the same syntax and scoping rules used by member initializer
lists:

// given variables v1,v2,...
[v1(move(v1)),&v2(cref(v2)),v3(whatever(v3,v4))](/*...*/){/*...*/}

with the obvious meaning ( in particular [v] and [&v] would be
equivalent to [v(v)] and [&v(v)], respectively ).
0 new messages