Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda expression in c++, OS X's clang vs GCC

A particular property of c++'s lambda expressions is to capture the variables in the scope in which they are declared. For example I can use a declared and initialized variable c in a lambda function even if 'c' is not sent as an argument, but it's captured by '[ ]':

 #include<iostream>
 int main ()
 {int c=5; [c](int d){std::cout<<c+d<<'\n';}(5);}

The expected output is thus 10. The problem arises when at least 2 variables, one captured and the other sent as an argument, have the same name:

 #include<iostream>
 int main ()
 {int c=5; [c](int c){std::cout<<c<<'\n';}(3);}

I think that the 2011 standard for c++ says that the captured variable has the precedence on the arguments of the lambda expression in case of coincidence of names. In fact compiling the code using GCC 4.8.1 on Linux the output I get is the expected one, 5. If I compile the same code using apple's version of clang compiler (clang-503.0.40, the one which comes with Xcode 5.1.1 on Mac OS X 10.9.4) I get the other answer, 3.

I'm trying to figure why this happens; is it just an apple's compiler bug (if the standard for the language really says that the captured 'c' has the precedence) or something similar? Can this issue be fixed?

EDIT

My teacher sent an email to GCC help desk, and they answered that it's clearly a bug of GCC compiler and to report it to Bugzilla. So Clang's behavior is the correct one!

like image 790
pier94 Avatar asked Jul 19 '14 10:07

pier94


2 Answers

From my understanding of the c++11 standard's points below:

5.1.2 Lambda expressions

3 The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below.

...

5 The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression’s parameter-declaration-clause and trailing-return-type respectively. 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.

...

14 For each entity captured by copy, an unnamed non static data member is declared in the closure type

A lambda expression like this...

int c = 5;

[c](int c){ std::cout << c << '\n'; }  

...is roughly equivalent to a class/struct like this:

struct lambda
{
    int c; // captured c

    void operator()(int c) const
    {
        std::cout << c << '\n';
    }
};

So I would expect the parameter to hide the captured member.

EDIT:

In point 14 from the standard (quoted above) it would seem the data member created from the captured variable is * unnamed *. The mechanism by which is it referenced appears to be independent of the normal identifier lookups:

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

It is unclear from my reading of the standard if this transformation should take precedence over parameter symbol lookup.

So perhaps this should be marked as UB (undefined behaviour)?

like image 106
Galik Avatar answered Oct 31 '22 11:10

Galik


From the C++11 Standard, 5.1.2 "Lambda expressions" [expr.prim.lambda] #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 id-expressions 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.

Also, from 3.3.3 "Block scope" [basic.scope.local] #2:

The potential scope of a function parameter name (including one appearing in a lambda-declarator) or of a function-local predefined variable in a function definition (8.4) begins at its point of declaration.

Names in a capture list are not declarations and therefore do not affect name lookup. The capture list just allows you to use the local variables; it does not introduce their names into the lambda's scope. Example:

int i, j;
int main()
{
    int i = 0;
    [](){ i; }; // Error: Odr-uses non-static local variable without capturing it
    [](){ j; }; // OK
}

So, since the parameters to a lambda are in an inner block scope, and since name lookup is done in the context of the lambda expression (not, say, the generated class), the parameter names indeed hide the variable names in the enclosing function.

like image 29
Eric M Schmidt Avatar answered Oct 31 '22 09:10

Eric M Schmidt