Topic: non-orthogonal design of lambda expressions


Author: Helmut Jarausch <jarausch@igpm.rwth-aachen.de>
Date: Sat, 12 Mar 2011 09:25:06 CST
Raw View
Hi,
in the current draft, 5.1.2 [expr.prim.lambda] 14 you can read

An entity is captured by copy if it is implicitly captured and the
capture-default is = or if it is explicitly captured with a capture that
does not include an &. For each entity captured by copy, an unnamed non-
static data member is declared in the closure type. The declaration
order of these members is unspecified. The type of such a data member is
the type of the corresponding captured entity if the entity is not a
reference to an object, or the referenced type otherwise. [ Note: If the
captured entity is a reference to a function, the corresponding data
member is also a reference to a function. \u2014 end note ]

But, contradicting to that, a lambda expression with capture default '='
does NOT keep a copy of a data member of a class if the lambda expression
occurs within a member function of that class.
It captures 'this' instead, and this IS capturing by REFERENCE.

Please explain why one has chosen such a non-orthogonal design (see below)
and what would have been broken if one had chosen a strictly orthogonal
design?

Many thanks,
Helmut.

E.g., look at the following example

#include <iostream>
#include <functional>

class StopWatch {
    typedef unsigned long long int Clock_Type;
    Clock_Type Now;

public:
    StopWatch() : Now( rdtsc() ) {}

    std::function<Clock_Type()> elapsed_time() const {
      return [=]() { return rdtsc() - Now; };
    }

private:
    static Clock_Type rdtsc() {
       Clock_Type x;
       unsigned a, d;
       __asm__ volatile("rdtsc" : "=a" (a), "=d" (d));
       return ((Clock_Type)a) | (((Clock_Type)d) << 32);;
    }
};

int main() {

    StopWatch SW;
    decltype(SW.elapsed_time()) ETime = SW.elapsed_time();

    // now, assume  SW goes out of scope, the following fails

    std::cout << "elapsed time: " << ETime() << "\n";

    return 0;
}

If the design of a lambda expression were orthogonal, it would

- in case of capturing by value - either [=] or spelled out in [ ]
    copy this value to a local variable of the callable object
    irrespectively where the value comes from.
    So, in case, the object which data member got captured by value
    doesn't exist anymore, such a lambda is still valid.

- in case of capturing by reference - either [&] or spelled out in [ ]
    with a leading &, such a variable might change whenever the underlying
    variable / object changes or fails to exist anymore.




--
Helmut Jarausch
Lehrstuhl fuer Numerische Mathematik
RWTH - Aachen University
D 52056 Aachen, Germany


[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: SG <s.gesemann@gmail.com>
Date: Tue, 15 Mar 2011 10:37:35 CST
Raw View
On 12 Mrz., 16:25, Helmut Jarausch wrote:
>
> in the current draft, 5.1.2 [expr.prim.lambda] 14 you can read
>
> An entity is captured by copy if it is implicitly captured and the
> capture-default is = or if it is explicitly captured with a capture that
> does not include an &. For each entity captured by copy, an unnamed non-
> static data member is declared in the closure type. The declaration
> order of these members is unspecified. The type of such a data member is
> the type of the corresponding captured entity if the entity is not a
> reference to an object, or the referenced type otherwise. [ Note: If the
> captured entity is a reference to a function, the corresponding data
> member is also a reference to a function. \u2014 end note ]
>
> But, contradicting to that, a lambda expression with capture default '='
> does NOT keep a copy of a data member of a class if the lambda expression
> occurs within a member function of that class.

Your conclusion seems to be wrong. This is not necessarily a
contradiction. It is not, if you don't consider that data member to be
a captured entity. In that case, only 'this' would be a captured
entity and it would be captured by "copy".

5.1.2/7 specifically discusses data member access:

N3225.pdf, 5.1.2/7:
  "The lambda-expression   s compound-statement yields the function-
   body (8.4) of the function call operator, but for purposes of name
   lookup (3.4), determining the type and value of this (9.3.2) and
   transforming idexpressions referring to non-static class members
   into class member access expressions using (*this) (9.3.1), the
   compound-statement is considered in the context of the lambda-
   expression. [ Example:

     struct S1 {
       int x, y;
       int operator()(int);
       void f() {
      [=]()->int {
           return operator()(this->x + y);
           // equivalent to S1::operator()(this->x + (*this).y)
           // this has type S1*
         };
       }
     };

      end example ]"

So, 'y' is transformed to be '(*this).y' and as far as capturing
things implicitly goes, we only need to capture the 'this' pointer in
this case. That makes 'y' not a captured entity (not even implicitly),
and hence, there is no contradiction.

Cheers!
SG


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Thu, 17 Mar 2011 12:22:36 CST
Raw View
On 03/15/2011 05:37 PM, SG wrote:

>
> On 12 Mrz., 16:25, Helmut Jarausch wrote:
>
>>
>> in the current draft, 5.1.2 [expr.prim.lambda] 14 you can read
>>
>> An entity is captured by copy if it is implicitly captured and the
>> capture-default is = or if it is explicitly captured with a capture th=
at
>> does not include an&. For each entity captured by copy, an unnamed non-
>> static data member is declared in the closure type. The declaration
>> order of these members is unspecified. The type of such a data member is
>> the type of the corresponding captured entity if the entity is not a
>> reference to an object, or the referenced type otherwise. [ Note: If the
>> captured entity is a reference to a function, the corresponding data
>> member is also a reference to a function. \u2014 end note ]
>>
>> But, contradicting to that, a lambda expression with capture default '=
='
>> does NOT keep a copy of a data member of a class if the lambda expressio=
n
>> occurs within a member function of that class.
>>
>
> Your conclusion seems to be wrong. This is not necessarily a
> contradiction. It is not, if you don't consider that data member to be
> a captured entity. In that case, only 'this' would be a captured
> entity and it would be captured by "copy".
>
> 5.1.2/7 specifically discusses data member access:
>
> N3225.pdf, 5.1.2/7:
>   "The lambda-expression=92s compound-statement yields the function-
>    body (8.4) of the function call operator, but for purposes of name
>    lookup (3.4), determining the type and value of this (9.3.2) and
>    transforming idexpressions referring to non-static class members
>    into class member access expressions using (*this) (9.3.1), the
>    compound-statement is considered in the context of the lambda-
>    expression. [ Example:
>
>      struct S1 {
>        int x, y;
>        int operator()(int);
>        void f() {
>       [=]()->int {
>            return operator()(this->x + y);
>            // equivalent to S1::operator()(this->x + (*this).y)
>            // this has type S1*
>          };
>        }
>      };
>
>    =97end example ]"
>
> So, 'y' is transformed to be '(*this).y' and as far as capturing
> things implicitly goes, we only need to capture the 'this' pointer in
> this case. That makes 'y' not a captured entity (not even implicitly),
> and hence, there is no contradiction.
>

My guess is that OP knows how all this works and that "y" is
equivalent to "this->y" and therefore "this" is captured by value.
But _effectively_, this makes "y" itself be captured by reference.

I didn't go into depth of lambda expressions, but my guess is that they
are not too much flexible. Let me ask a question related to this:

 S1 s1;

      [=]() -> int {
     Q: How do I capture "s1.x" itself, instead of capturing "s1"
        and accessing it's member "x"?
 }

What is the reason for "s1.x" and "this->x" capturing "s1" and "this"
respectively, instead of capturing the final lvalue? Is it only
a design choice?

Capturing the final lvalue would satisfy OP's worries. And if someone
really wants to capture "s1" and "this", one can explicitly write
"[&s1, =this]" and will have the behavior that is now the default one.

To me, this sounds more logical... Especially for capture by value
where we most likely want to capture "x". It would seem that
when writing "s1.x", we most likely do not want to capture
a copy of "s1", but this is what will happen. What do you think?

--
Dragan


[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: SG <s.gesemann@gmail.com>
Date: Fri, 18 Mar 2011 12:14:21 CST
Raw View
On 17 Mrz., 19:22, Dragan Milenkovic wrote:
> I didn't go into depth of lambda expressions, but my guess is that they
> are not too much flexible. Let me ask a question related to this:
>
>  S1 s1;
>
>  [=]() -> int {
>      Q: How do I capture "s1.x" itself, instead of capturing "s1"
>         and accessing it's member "x"?
>  }

   S1 s1;
   auto&& s1x = s1.x;
      [s1x]()->int { ..... }

At least this works with a recent GCC version. I'm not 100% sure
whether this is supposed to work according to the standard draft. The
draft only speaks about "variables with automatic storage duration
declared in the reaching scope of the local lambda expression" when it
comes to the identifiers in the capture clause (see 5.1.2/10). But a
"variable" is introduced by a declaration of an "object". And since
references don't declare objects but just aliases for objects (or
functions), it appears as if my code example is not legal C++0x code.

As I said in the other thread I created about lambdas, I find the
capturing clause part of the current draft unnecessarily restrictive
in that regard. That is, assuming I did not misunderstand something.

> What is the reason for "s1.x" and "this->x" capturing "s1" and "this"
> respectively, instead of capturing the final lvalue? Is it only
> a design choice?

What if you refer to s1 AND s1.x in the lambda's body? Do you want the
lambda object store two x-es? (as a member of s1 and as an isolated
copy?) How can I access the x member of the captured s1?

  S1 s1;
      [=] {
    S1 const* ptr = &s1; // s1 is captured by copy, right?
    // Is s1.x a member of s1 or an unrelated copy?
    bool wtf = (&s1.x == &ptr->x);
  }

If you ask me, the only thing that makes any sense is to capture only
top-level entities implicitly that are declared in the surrounding
scope (including the 'this'-pointer).

Still, I should be allowed to explicitly mention other identifiers in
a capture clause like local references or data members so that I can
at least *explicitly* capture them "by copy".

> It would seem that
> when writing "s1.x", we most likely do not want to capture
> a copy of "s1",

I disagree.

Cheers!
SG


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Sat, 19 Mar 2011 13:21:36 CST
Raw View
[2nd try after > 24 h]

Am 17.03.2011 19:20, schrieb Edward Diener:
>
> On 3/12/2011 10:27 AM, Daniel Kr=FCgler wrote:
>
>> I do not understand why, given:
>>>
>>> Class template: template<class X,class Y> class ATemplate {};
>>>
>>> Template alias: template<class T> using CTAlias = ATemplate<T,int>;
>>>
>>> you are saying that ATemplate<T,int> is a type-id
>>>
>>
>> Yes, because ATemplate<T,int> is a type for every T that is a type
>> itself. T must be a type, because the template declaration of
>> CTAlias says that it has a type parameter. Of-course we could just
>> follow the grammar of /type-id/ and could confirm that
>> ATemplate<T,int> satisfies the criteria for a type-id.
>>
>> but:
>>>
>>> Function template: template<class X,class Y> void AFTemplate(X,Y) { }
>>>
>>
>> Note the difference: A class template describes a family of classes,
>> and each class is a type. An alias template describes a family of
>> types. But a function template describes a family of *functions*,
>> *not*
>> a family of function types and also not a "function template type".
>>
>>
> I am going to focus on what you say here, because it is the crux of why
> I do not understand your explanation.
>
> I do not understand how you can say that a function template describes a
> family of functions but that each function in that family of functions is
> not a type.
>
> Class template: template<class X> class ClassTemplate { };
>
> for each instantiation of ClassTemplate we have a different type:
>
> ClassTemplate<int> is a type.
> ClassTemplate<short> is another type.
> etc. etc.
>
> Function template: template<class X> void FunctionTemplate (X) { }
>
> for each instantiation of FunctionTemplate we have a different type:
>
> FunctionTemplate<int> is the type 'void (int)'
> FunctionTemplate<short> is the type 'void (short)'
> etc. etc.

This is a false assumption, consider:

template<class>
void foo();

For every type X, the type of foo<X> is void();

Or:

template<int>
void bar();

For every value N, the type of foo<N> is void();

This extends to mixed cases, where only a subset of the actual
template parameters of a function template influences the type, e.g.

template<class, class T>
int soso(T);

Note that these relaxed rules do *not* apply to class templates.
Indeed *every* template parameter of a class template is part of the
type or the resulting instantiated type:

template<class>
struct Foo {};

The type of Foo<char> is different than the type of Foo<signed char>,
i.e. this fundamental difference allows us to use a class template to
determine type-sameness (E.g. std::is_same).

> Surely a function instantiated from a function template is a type just as a
> class instantiated from a class template is a type.

There is no guaranteed 1:1 relation as there exists for class templates.

> Regarding type-id, in section 8.1 of the working standard I read:
>
> "To specify type conversions explicitly, and as an argument of sizeof,
> alignof, new, or typeid, the name of
> a type shall be specified. This can be done with a type-id, which is
> syntactically a declaration for a variable
> or function of that type that omits the name of the entity."
>
> It appears to me, from the above, that a type-id can refer to a function
> type.

You are misinterpreting the wording. What the wording says is, that
given the variable declaration

int* x;

we determine the type-id of x and get the type by removing the name 'x':

int*

The same applies to a function declaration:

void foo(int);

Take away the function name 'foo' and we get

void(int)

But this does *not* say that a function declaration or a variable
declaration *is* a type-id, it only says how we can *obtain* a type-id
given some function declaration of variable declaration - this is a
big difference.

> Now you may be arguing that for the purposes of an alias template ( I will
> use your terminology since I also say 'class template' rather than
> 'template class' and 'function template' rather than 'template function' )

[Note that this is not *my* terminology, but the revised terminology
of the C++0x standard]

> the 'type-id' part can refer to a class template and not a function template.

This has nothing to do with "referring", it is simply the grammar. The
grammar of a function (template) declaration does not match the
grammar of a type-id. A type-id describes a type, but a function is
*not* a type, but it has one. The special nature of function templates
versus class templates becomes clearer if I would suggest to extend
templates to allow for *variable* templates, e.g.

template<class T>
T x;

Note that this artificial "variable template" has many aspects in
common with function templates. Look now again at function templates
and compare them to class templates: Isn't this a completely different
thing? Note in particular that *variable templates* would have the
same properties regarding types as function templates have, e.g.

template<class>
int y;

Each instance of the variable y, e.g. y<int> compared to y<bool> has
the same type. Again notice that for class-templates there does not
exist such a relaxed type-relation.

HTH & Greetings from Bremen,

- Daniel Kr   gler


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]