본문 바로가기
놀기/C, C++

C++ 람다 식 공부 (2)

by Hi~ 2021. 7. 22.

2021.07.21 - [일하기/C++] - C++ 람다 식 공부 (1)

 

lambda의 포맷

 

매개 변수 목록

람다는 변수를 캡처하고 입력 매개 변수를 수락할 수 있습니다. 매개 변수 목록(표준 구문의 람다 선언자)은 선택 사항이며 대부분의 경우 함수의 매개 변수 목록과 유사합니다.

 

auto y = [] (int first, int second)
{
    return first + second;
};
● 바깥의 변수를 사용하지는 않고 일반 함수의 매개변수처럼 first와 second를 만들어 사용

 

C++14에서 매개 변수 형식이 제네릭인 경우 auto 키워드를 형식 지정자로 사용할 수 있습니다. 이 키워드는 함수 호출 연산자를 템플릿으로 만들도록 컴파일러에 지시합니다. 매개 변수 목록의 각 auto 인스턴스는 고유 형식 매개 변수와 동일합니다.

auto y = [] (auto first, auto second)
{
    return first + second;
};
● auto 키워드 사용 가능


람다 식은 다른 람다 식을 인수로 사용할 수 있습니다. 자세한 내용은 람다 식 예제 문서에서 "고차 람다 식"을 참조하세요.

람다 식에 인수를 전달하지 않거나 람다 선언자가 exception-specification , trailing-return-type 또는 mutable를 포함하지 않으면 빈 괄호를 생략할 수  있습니다.

● 위와 같은 조건에서는 괄호가 생략될 수 있으니 그 부분이 없다고 당황하지 말자.

 

Mutable 사양

일반적으로 람다의 함수 호출 연산자는 const-by-value이지만 mutable 키워드를 사용하면 취소됩니다. 변경 가능한 데이터 멤버를 생성하지 않습니다. mutable 사양을 사용하면 람다 식의 본문이 값으로 캡처되는 변수를 수정할 수 있습니다. 이 문서의 후반부에 있는 몇 가지 예제에서는 mutable를 사용하는 방법을 보여준다.

● const 키워드가 붙은 함수 일 경우 내부에서 변수에 대한 변경을 할 수 없는데 변경이 필요한 경우가 있다 이럴 경우 mutable 키워드를 사용하면 해당 변수를 수정 가능하다. 
● 변수에 mutable을 붙이는 경우도 있고 함수 또는 람다 식에 붙이는 경우도 있다. 여러 경우가 있으니 mutable에 대해 이해하고 소스 코드를 보면 될 것 같다.

 

예외 사양

예외 사양을 사용하여 noexcept 람다 식이 예외를 throw하지 않는다는 것을 나타낼 수 있습니다. 일반 함수와 마찬가지로 람다 식이 예외 사양을 선언하고 람다 본문이 예외를 throw 하는 경우 Microsoft C++ 컴파일러는 다음과 같이 경고 C4297을 noexcept 생성합니다.


// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
   []() noexcept { throw 5; }();
}
자세한 내용은 예외 사양(throw)을참조하세요.

● 뭔 말인지 모르겠다. 다음에 확인하자. 

 

반환 형식

람다 식의 반환 형식은 자동으로 추론됩니다. 후행 반환 형식을 auto 지정하지 않는 한 키워드를 사용할 필요가 없습니다. trailing-return-type은 일반 함수 또는 멤버 함수의 반환 형식 부분과 유사합니다. 그러나 반환 형식은 매개 변수 목록을 따라야 하며 반환 형식 앞에 trailing-return-type 키워드 -> 를 포함해야 합니다.

람다 본문에 return 문이 하나만 포함된 경우 람다 식의 반환 형식 부분을 생략할 수 있습니다. 또는 식이 값을 반환하지 않는 경우 람다 본문에 단일 return 문이 포함되어 있으면 컴파일러는 반환 식의 형식에서 반환 형식을 추론합니다. 그렇지 않으면 컴파일러는 반환 형식을 로 void 추론합니다. 이 원칙을 설명하는 다음 예제 코드 조각을 고려하세요.

 

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing
                                  // return type from braced-init-list isn't valid


람다 식은 다른 람다 식을 반환 값으로 생성할 수 있습니다. 자세한 내용은 람다 식의 예에서 "높은 순서의 람다 식"을 참조하세요. 

● 예제 코드를 보면 대략 뭔 말인지를 알 것 같다.

 

람다 본문

람다 식의 람다 본문은 복합 문입니다. 일반 함수 또는 멤버 함수의 본문에 허용되는 모든 것을 포함할 수 있습니다. 일반 함수와 람다 식 모두의 본문은 다음과 같은 종류의 변수에 액세스 할 수 있습니다.

  • 앞의 설명대로 바깥쪽 범위에서 캡처된 변수
  • 매개 변수
  • 로컬로 선언된 변수
  • 클래스 내에서 선언되고 this가 캡처되었을 때의 클래스 데이터 멤버
  • 정적 스토리지 기간이 있는 모든 변수(예: 전역 변수)

다음 예제에는 n 변수를 값 별로 명시적으로 캡처하고 m 변수를 참조별로 암시적으로 캡처하는 람다 식이 포함되어 있습니다.

 

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}

 

실행 결과

5
0

 

n 변수는 값별로 캡처되므로 람다 식을 호출한 후 해당 값이 0으로 유지됩니다. mutable 사양을 사용하면 n 람다 내에서 수정할 수 있습니다.

람다 식은 자동 스토리지 기간이 있는 변수만 캡처할 수 있습니다. 그러나 람다 식의 본문에 정적 스토리지 기간이 있는 변수를 사용할 수 있습니다. 다음 예제는 generate 함수 및 람다 식을 사용하여 vector 개체의 각 요소에 값을 할당합니다. 람다 식은 정적 변수를 수정하여 다음 요소의 값을 생성합니다.

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}


자세한 내용은 generate을 참조하세요.

generate 링크

 

다음 코드 예제에서는 이전 예제의 함수를 사용하고 C++ 표준 라이브러리 알고리즘을 사용하는 람다 식의 예를 generate_n 추가합니다. 이 람다 식은 vector 개체의 요소를 이전의 두 요소의 합에 할당합니다. mutable 키워드는 람다 식의 본문이 람다 식이 값으로 캡처하는 외부 변수 및 의 복사본을 수정할 수 있도록 x y 사용됩니다. 람다 식이 원래 변수 x 및 y를 값 별로 캡처하기 때문에 람다가 실행된 후에도 값이 1로 유지됩니다.

 

// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename C> void print(const string& s, const C& c) {
    cout << s;

    for (const auto& e : c) {
        cout << e << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}

int main()
{
    // The number of elements in the vector.
    const int elementCount = 9;

    // Create a vector object with each element set to 1.
    vector<int> v(elementCount, 1);

    // These variables hold the previous two elements of the vector.
    int x = 1;
    int y = 1;

    // Sets each element in the vector to the sum of the
    // previous two elements.
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // lambda is the 3rd parameter
        // Generate current value.
        int n = x + y;
        // Update previous two values.
        x = y;
        y = n;
        return n;
    });
    print("vector v after call to generate_n() with lambda: ", v);

    // Print the local variables x and y.
    // The values of x and y hold their initial values because
    // they are captured by value.
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(v);
    print("vector v after 1st call to fillVector(): ", v);
    // Fill the vector with the next sequence of numbers
    fillVector(v);
    print("vector v after 2nd call to fillVector(): ", v);
}

 

실행결과

vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

자세한 내용은 generate_n을 참조하세요.

generate_n 링크

 

constexpr 람다 식

Visual Studio 2017 버전 15.3 이상(에서 사용 /std:c++17 constexpr 가능): 캡처되거나 도입된 각 데이터 멤버의 초기화가 상수 식 내에서 허용되는 경우 람다 식을 로 선언하거나 상수 식에서 사용할 수 있습니다.

int y = 32;
auto answer = [y]() constexpr
{
    int x = 10;
    return y + x;
};

constexpr int Increment(int n)
{
    return [n] { return n + 1; }();
}


람다의 결과가 constexpr 함수의 요구 사항을 충족하면 람다는 암시적 constexpr 함수다.

auto answer = [](int n)
{
    return 32 + n;
};

constexpr int response = answer(10);


람다가 암시적 또는 명시적으로 constexpr 이면 함수 포인터로 변환하면 constexpr 함수가 생성됩니다. 

auto Increment = [](int n)
{
    return n + 1;
};

constexpr int(*inc)(int) = Increment;

 

'놀기 > C, C++' 카테고리의 다른 글

C++ 람다 식 공부 (6) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (5) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (4) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (3)  (0) 2021.07.22
C++ 람다 식 공부 (1)  (0) 2021.07.21

댓글