C++11, Lambda Expressions #2

Short Articles 2012. 2. 1. 18:52
앞 글에 이어 우선 lambda-declarator 부분을 잠시 보고, capture 와 관련된 부분들을 다시 살펴 보겠습니다.

+ lambda-declarator
lambda-declarator 는

(파라미터 선언 절) mutable exception-specification attribute-specification trailing-return-type


의 형식으로 작성되고,
이 중에서 (파라미터 선언 절) 외의 부분들은 필요에 따라 적어주면 되는 부분들입니다.
lambda-declarator 에 포함된 사항들은 C++ lambda 의 새롭게 추가된 사항이 아닌 기존 C++ 에 이미 정의된 사항들 이므로 자세한 내용은 생략 하겠습니다.

간단한 예시 코드,
이 람다식이 포함하는 closure object 는 ecode 변수를 copy 로 capture 합니다.
mutable 키워드는 capture 를 compound-statement 내에서 수정 가능함을 나타내고,
뒤따라오는 throw(int) 는 int type 의 예외를 던진다는 것을 알립니다. ->int 는 compound-statement 의
리턴 타입이 int 형임을 지정합니다.

[=](char const *s) throw(int) ->int ...  와 같이 mutable 키워드를 지정하지 않는 경우
closure 에서 public 으로 선언(정의)되는 function call operator 는 기본 const 입니다.

다시 capture 관련 이야기로 돌아가서...

local-lambda expression
block scope 바로 아래 범위에 닿아 있는 람다 표현식을 local lambda expression 이라고 합니다.
local lambda expression 의 영향 범위는 자신의 한겹 위쪽 블럭과 아래쪽으로 포함되는 영역 입니다.
(함수와 함수의 파라미터를 포함해서..)
이 유효 범위 사이에는 또다른 람다 표현식이 끼어들 수도 있습니다.


위 코드에서 람다 표현식의 범위는 PrintPoint 함수의 블럭과 표현식이 포함하고 있는 출력 코드 부분입니다.
이 경우  name 은 local lambda expression 의 유효 범위 밖에 있기 때문에 에러가 납니다.
이 때에는 명시적으로 this 를 capture 하거나 PrintPoint 의 지역 변수로 const char* 하나를 선언해서
name 의 값을 받은 후 이것을 명시적으로 capture 하는 식으로 에러를 피할 수 있습니다.

lambda 표현식의 capture-list 에 대한 name look up 은 일반적인 unqualified name look up
(:: , std:: , boost::  등으로 특정 짓지 않은 entitiy 에 대한 name look up) 에 따릅니다. 
각각의 룩업은 local lambda expression 의 유효 범위 내에서 automatic storage duration 으로
선언된 변수를 찾습니다.

automatic storage duration :
자동으로 생명주기가 관리되는 것들을 말한다. variable with automatic storage duration 이라 하면,
C++ 에서는 주로 stack 에 생성되는 지역 변수등을 일컫는다.

 
ODR & lambda-expression

(참고. cl.exe 는 아직 implicitly capture 를 제대로 지원하지 못 하고 있습니다. )

람다 표현식이 capture-default 를 포함하고 표현식의 odr 사용이 this 나 automatic storage duration
변수 그리고 odr이 사용된 entity 가 명시적으로 캡처(explicitly capture)되지 않았다면,
odr이 사용된 entity 는 임시적으로 캡처(implicitly capture) 되었다고 합니다.

다음은 표준 문서에 나오는 implicitly capture 에 대한 예제 코드 입니다.
코드 작성과 테스트는 eclipse with g++4.6.2 로 했습니다.
이 코드를 컴파일하면 다음과 같은 에러를 보게 됩니다.

../src/hello.cpp: In lambda function:

../src/hello.cpp:38:18: error: 'j' is not captured

../src/hello.cpp: In lambda function:

../src/hello.cpp:39:14: error: 'n' is not captured

../src/hello.cpp:43:11: error: 'i' is not captured

../src/hello.cpp: In lambda function:

../src/hello.cpp:45:5: error: unable to deduce 'auto' from '<expression error>'


N 과 M 은 m1 에 의해서 implicitly capture 되었기 때문에 괜찮지만,
j 는 m3 에 의해서 캡처 되지 않았고 n 은 m4 에 의해서는 implicitly capture 되지만
m4 를 둘러싸고 있는 m3 가 n 을 캡처 하지 않았기 때문에 에러입니다.
그리고 i 는 유효범위 밖에 있으므로 역시 에러가 나고 있습니다.

다음과 같이 명시적으로 n, j 를 캡처하고 x += i; 를 주석 처리 하면
에러없이 컴파일 되는 것을 확인할 수 있습니다.
 
기본 인자로 lambda-expression 이 올 때, 어떤 entity 도 명시적이든 암시적이든 캡처할 수 없습니다.
이 부분은 개인적으로 그다지 중요한 부분은 아니라고 생각하는데,
아무튼 람다 표현식이 기본인자로 쓰일 경우 

void foo(int = ( []{ return 0;}() ));

처럼 상수식이 반환되는 경우는 ok 지만 어떤 것이든 캡처를 하는 경우는 에러입니다.
별다른 내용은 없습니다.

entity - captured by copy
entity 가 implicitly capture 되었고 capture default 가 '=' 이거나
entity 가 explicitly capture 되었고 '&' 를 포함하지 않으면,
이 entity 는 copy 로 캡처 되었다고 합니다.

by copy 로 capture 캡처된 entity 는 closure 의 non-static unnamed 데이터 멤버로 선언됩니다.
이 때, 선언되는 순서에 정해진 규칙은 없습니다. entity 가 객체나 그 밖의 타입에 대한 참조가
아닐 경우 이렇게 되며, int &p = value; 에서의 p 같은 참조 변수는 captured by copy 되지 않습니다.

entity - captured by reference 
명시적으로 또는 암시적으로 캡처되지만 copy 로 캡처되지 않는 entity 는 reference 로 캡처됩니다.

nested lambda expressions 에서 한 표현식을 다른 표현식이 바로 둘러 싸는 경우
표현식 m1이 m2 를 둘러 싸고 있다고 할 때,
m1 이 by copy 로 캡처한 entity 는 m1 의 closure 타입의 non-static data member 에 대응되는 것으로
m2 에 캡처됩니다. 그리고 m1 이 by reference 로 캡처한 entity 는 m1 이 캡처한 것과 동일한 entity 로
m2 가 캡처합니다.
이 코드의 결과 값은 다음과 같습니다.

123234



 
람다 표현식에 대한 글은 여기서 이만 줄이겠습니다.
표준 문서에 몇 가지 추가적인 사항에 대한 설명이 나오므로 더 자세한 사항을 원하시는 분은
표준 명세를 찾아보시기 바랍니다.
 
: