closure type for lambda-expressions and constness

Discussion in 'C++' started by Paul Bibbings, Apr 20, 2010.

  1. I'm currently trying to cememt my understanding of n3091
    [expr.prim.lambda]. In particular, however, I'm wanting to be sure that
    I understand how the constness of the closure type's public function
    call operator affects the viability of assignments within its function
    body, depending upon whether capture occurs by copy or by reference.

    In §5.1.2/5 it says that "This function call operator is declared const
    if and only if the lambda-expression's parameter-declaration-clause is
    not followed by mutable."

    Now, for objects captured by copy the situation appears clear. In
    §5.1.2/14 it says "For each entity captured by copy, an unnamed
    non-static data member is declared in the closure type." In this
    instance it is easy to understand that such unnamed non-static data
    members may not be assigned to in the body of the function call operator
    on an object of closure type that is not declared mutable.

    So, in the following - forgetting for the moment that the `algorithm'
    itself fails simply because it has selected capture by copy - the code
    fails on the grounds that it omits to add mutable after the
    lambda-expression's parameter-declaration-clause:

    #include <algorithm>

    int main()
    {
    double d_array[] = { 1.1, 2.2, 3.3, 4.4 };
    double sum = 0;
    std::for_each(d_array,
    d_array + 4,
    [=](double d) { // error: not mutable
    sum += d;
    });
    }

    What is not clear to me from the specific wording, however, is how this
    should behave if we `correct' the code to fit the algorithm and specify
    a default capture-by-reference in the lambda-introducer.

    Intuitively I would expect the following to be okay:

    #include <algorithm>

    int main()
    {
    // as before

    std::for_each(d_array,
    d_array + 4,
    [&](double d) { // OK: not mutable?
    sum += d;
    });
    }

    and certainly gcc-4.5.0 agrees:

    21:47:59 Paul Bibbings@JIJOU
    /cygdrive/d/CPPProjects/CLCPPM $i686-pc-cygwin-gcc-4.5.0 -std=c++0x
    -pedantic -c non_mutable_lambda.cpp

    21:48:32 Paul Bibbings@JIJOU
    /cygdrive/d/CPPProjects/CLCPPM $

    However, I then notice where it says - in §5.1.2/15:
    "It is unspecified whether additional unnamed non-static data members
    are declared in the closure type for entities captured by reference."

    To my reading, this appears to introduce an ambiguity over whether the
    second example above is valid or not since, where this were to be the
    case - that is, where additional non-static data members /are/ declared
    for entities captured by reference - it would appear that the assignment
    in the function body should fail on the same grounds as for the case of
    capture by copy - that it constituted an attempt to assign to a member
    of an object of const closure type.

    So, any clarification on this point would be accepted gratefully.

    Regards

    Paul Bibbings
     
    Paul Bibbings, Apr 20, 2010
    #1
    1. Advertising

  2. Paul Bibbings

    SG Guest

    On 20 Apr., 23:06, Paul Bibbings wrote:
    > [...]
    >       double sum = 0;
    >       std::for_each(d_array,
    >                     d_array + 4,
    >                     [=](double d) {  // error: not mutable
    >                        sum += d;
    >                     });
    > [...]
    > double sum = 0;
    > std::for_each(d_array,
    > d_array + 4,
    > [&](double d) { // OK: not mutable?
    > sum += d;
    > });
    >
    > and certainly gcc-4.5.0 agrees:
    >
    >    21:47:59 Paul Bibbings@JIJOU
    >    /cygdrive/d/CPPProjects/CLCPPM $i686-pc-cygwin-gcc-4.5.0 -std=c++0x
    >       -pedantic -c non_mutable_lambda.cpp
    >
    >    21:48:32 Paul Bibbings@JIJOU
    >    /cygdrive/d/CPPProjects/CLCPPM $
    >
    > However, I then notice where it says - in §5.1.2/15:
    >    "It is unspecified whether additional unnamed non-static data
    > members are declared in the closure type for entities captured
    > by reference."
    >
    > To my reading, this appears to introduce an ambiguity over whether the
    > second example above is valid or not since, where this were to be the
    > case - that is, where additional non-static data members /are/ declared
    > for entities captured by reference - it would appear that the assignment
    > in the function body should fail on the same grounds as for the case of
    > capture by copy - that it constituted an attempt to assign to a member
    > of an object of const closure type.


    Keep in mind that a reference itself is inherently constant. Once
    initialized, it cannot be made to refer to a different object. The
    object that is referred to can still be modified even if the reference
    is a non-static member and the member function that does so is const:

    class foo {
    int& ref;
    public:
    explicit foo(int&r) : ref(r) {}
    void blank() const { ref=0; }
    // ^^^^^ OK
    };

    I think the purpose of the text you quoted is simply to allow the
    compiler to generate "smaller" closure types. Instead of a reference
    member for each function-local variable from the surrounding scope
    that is captured by reference, a compiler might include a single
    pointer to the "stack frame" and access the objects through this
    pointer via fixed offsets. Whether the closure type includes reference
    members or just a stack pointer is unspecified. But it doesn't change
    the fact that the lambda object's function call operator can modify
    variables that have been captured by reference even without the
    mutable keyword. Just like pointers, references don't propagate top-
    level constness down to the pointee.

    Cheers,
    SG
     
    SG, Apr 21, 2010
    #2
    1. Advertising

  3. SG <> writes:

    > On 20 Apr., 23:06, Paul Bibbings wrote:
    >> [...]
    >> double sum = 0;
    >> std::for_each(d_array,
    >> d_array + 4,
    >> [=](double d) { // error: not mutable
    >> sum += d;
    >> });
    >> [...]
    >> double sum = 0;
    >> std::for_each(d_array,
    >> d_array + 4,
    >> [&](double d) { // OK: not mutable?
    >> sum += d;
    >> });
    >>
    >> and certainly gcc-4.5.0 agrees:
    >>
    >> 21:47:59 Paul Bibbings@JIJOU
    >> /cygdrive/d/CPPProjects/CLCPPM $i686-pc-cygwin-gcc-4.5.0 -std=c++0x
    >> -pedantic -c non_mutable_lambda.cpp
    >>
    >> 21:48:32 Paul Bibbings@JIJOU
    >> /cygdrive/d/CPPProjects/CLCPPM $
    >>
    >> However, I then notice where it says - in §5.1.2/15:
    >> "It is unspecified whether additional unnamed non-static data
    >> members are declared in the closure type for entities captured
    >> by reference."
    >>
    >> To my reading, this appears to introduce an ambiguity over whether the
    >> second example above is valid or not since, where this were to be the
    >> case - that is, where additional non-static data members /are/ declared
    >> for entities captured by reference - it would appear that the assignment
    >> in the function body should fail on the same grounds as for the case of
    >> capture by copy - that it constituted an attempt to assign to a member
    >> of an object of const closure type.

    >
    > Keep in mind that a reference itself is inherently constant. Once
    > initialized, it cannot be made to refer to a different object. The
    > object that is referred to can still be modified even if the reference
    > is a non-static member and the member function that does so is const:
    >
    > class foo {
    > int& ref;
    > public:
    > explicit foo(int&r) : ref(r) {}
    > void blank() const { ref=0; }
    > // ^^^^^ OK
    > };
    >
    > I think the purpose of the text you quoted is simply to allow the
    > compiler to generate "smaller" closure types. Instead of a reference
    > member for each function-local variable from the surrounding scope
    > that is captured by reference, a compiler might include a single
    > pointer to the "stack frame" and access the objects through this
    > pointer via fixed offsets. Whether the closure type includes reference
    > members or just a stack pointer is unspecified. But it doesn't change
    > the fact that the lambda object's function call operator can modify
    > variables that have been captured by reference even without the
    > mutable keyword. Just like pointers, references don't propagate top-
    > level constness down to the pointee.


    Of course. Thank you.

    That makes perfects sense, now (or should I say, again; I know this, but
    for some reason, the central point you make in your last sentence just
    seems to want to take a vacation from my memory every once in a
    while. :)

    Regards

    Paul Bibbings
     
    Paul Bibbings, Apr 22, 2010
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Roman Suzi
    Replies:
    13
    Views:
    608
    Bengt Richter
    Jan 7, 2005
  2. Ted Lilley

    lambda closure question

    Ted Lilley, Feb 19, 2005, in forum: Python
    Replies:
    27
    Views:
    747
    Karl Anderson
    Feb 22, 2005
  3. Replies:
    14
    Views:
    829
    Ian Collins
    Apr 4, 2006
  4. Julian Mehnle
    Replies:
    0
    Views:
    239
    Julian Mehnle
    Jul 17, 2003
  5. Haochen Xie
    Replies:
    4
    Views:
    245
    Haochen Xie
    Mar 17, 2013
Loading...

Share This Page