C++ constexpr Lambda | "컴파일 타임 람다" 가이드

C++ constexpr Lambda | "컴파일 타임 람다" 가이드

이 글의 핵심

C++ constexpr Lambda에 대한 실전 가이드입니다.

들어가며

C++17 constexpr 람다는 컴파일 타임에 실행 가능한 람다 표현식입니다. 메타프로그래밍, 컴파일 타임 계산, 타입 검증 등에 활용되며, 런타임 비용 없이 강력한 기능을 제공합니다.


1. constexpr 람다 기본

C++17 암시적 constexpr

#include <iostream>

// C++17: 람다가 암시적으로 constexpr
auto add =  {
    return a + b;
};

int main() {
    // 컴파일 타임 사용
    constexpr int result1 = add(3, 4);  // 7
    static_assert(add(3, 4) == 7);
    
    // 런타임 사용도 가능
    int x = 10, y = 20;
    int result2 = add(x, y);  // 30
    
    std::cout << result1 << ", " << result2 << std::endl;
    return 0;
}

핵심 개념:

  • C++17부터 람다가 암시적으로 constexpr
  • 조건: 람다 본문이 constexpr 요구사항을 만족하면
  • 컴파일 타임과 런타임 모두 사용 가능

명시적 constexpr

// 명시적으로 constexpr 지정
constexpr auto square =  constexpr {
    return x * x;
};

constexpr int result = square(5);  // 25
static_assert(square(5) == 25);

// 배열 크기로 사용
int arr[square(4)];  // 크기 16

2. 컴파일 타임 계산

예제 1: Factorial

#include <iostream>

constexpr auto factorial =  {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
};

int main() {
    // 컴파일 타임 계산
    constexpr int f5 = factorial(5);  // 120
    static_assert(factorial(5) == 120);
    
    // 배열 크기로 사용
    int arr[factorial(4)];  // 크기 24
    
    std::cout << "5! = " << f5 << std::endl;
    std::cout << "배열 크기: " << sizeof(arr) / sizeof(int) << std::endl;
    
    return 0;
}

예제 2: 거듭제곱 (템플릿 활용)

#include <iostream>

template<int N>
constexpr auto power =  {
    int result = 1;
    for (int i = 0; i < N; i++) {
        result *= base;
    }
    return result;
};

int main() {
    constexpr int p2 = power<3>(2);  // 2^3 = 8
    constexpr int p3 = power<5>(3);  // 3^5 = 243
    
    static_assert(power<3>(2) == 8);
    static_assert(power<5>(3) == 243);
    
    std::cout << "2^3 = " << p2 << std::endl;
    std::cout << "3^5 = " << p3 << std::endl;
    
    return 0;
}

예제 3: 배열 초기화

#include <array>
#include <iostream>

template<size_t N>
constexpr auto makeArray =  {
    std::array<int, N> arr{};
    for (size_t i = 0; i < N; i++) {
        arr[i] = i * i;
    }
    return arr;
};

int main() {
    constexpr auto squares = makeArray<5>();
    // {0, 1, 4, 9, 16}
    
    for (int val : squares) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

3. 타입 검사 및 메타프로그래밍

타입 검사

#include <type_traits>
#include <iostream>

constexpr auto isIntegral =  {
    return std::is_integral_v<decltype(value)>;
};

constexpr auto isFloating =  {
    return std::is_floating_point_v<decltype(value)>;
};

int main() {
    static_assert(isIntegral(10));
    static_assert(!isIntegral(3.14));
    static_assert(isFloating(3.14));
    static_assert(!isFloating(10));
    
    std::cout << "타입 검사 통과" << std::endl;
    return 0;
}

조건부 컴파일

#include <type_traits>
#include <iostream>

constexpr auto processValue =  {
    if constexpr (std::is_integral_v<decltype(value)>) {
        return value * 2;
    } else if constexpr (std::is_floating_point_v<decltype(value)>) {
        return value * 1.5;
    } else {
        return value;
    }
};

int main() {
    constexpr int i = processValue(10);      // 20
    constexpr double d = processValue(10.0); // 15.0
    
    static_assert(i == 20);
    static_assert(d == 15.0);
    
    std::cout << i << ", " << d << std::endl;
    return 0;
}

4. 제약 사항

허용되는 것

// ✅ 허용: 기본 연산
constexpr auto add =  {
    return x + y;
};

// ✅ 허용: 제어문
constexpr auto max =  {
    return (a > b) ? a : b;
};

// ✅ 허용: 루프
constexpr auto sum =  {
    int result = 0;
    for (int i = 1; i <= n; i++) {
        result += i;
    }
    return result;
};

// ✅ 허용: constexpr 변수 캡처
constexpr int multiplier = 10;
constexpr auto scale = [multiplier](int x) {
    return x * multiplier;
};

허용되지 않는 것

// ❌ 불가능: static 지역 변수
constexpr auto invalid1 =  {
    static int count = 0;  // 에러!
    return x;
};

// ❌ 불가능: 예외 던지기
constexpr auto invalid2 =  {
    if (x < 0) {
        throw std::runtime_error("음수");  // 에러!
    }
    return x;
};

// ❌ 불가능: 비constexpr 함수 호출
void nonConstexprFunc() { }

constexpr auto invalid3 =  {
    nonConstexprFunc();  // 에러!
    return 0;
};

// ❌ 불가능: 비constexpr 변수 캡처 (컴파일 타임 사용 시)
int nonConstexpr = 10;
constexpr auto invalid4 = [nonConstexpr]() {
    return nonConstexpr;  // 런타임에서는 OK, 컴파일 타임에서는 에러
};
// constexpr int x = invalid4();  // 에러!

5. 자주 발생하는 문제

문제 1: 비constexpr 변수 캡처

#include <iostream>

int main() {
    int x = 10;  // 비constexpr 변수
    
    // ❌ 컴파일 타임 사용 불가
    constexpr auto lambda1 = [x]() {
        return x * 2;
    };
    // constexpr int result = lambda1();  // 에러!
    
    // ✅ 런타임 사용은 가능
    int runtimeResult = lambda1();  // OK
    
    // ✅ constexpr 변수 캡처
    constexpr int y = 10;
    constexpr auto lambda2 = [y]() {
        return y * 2;
    };
    constexpr int compileTimeResult = lambda2();  // OK
    
    std::cout << runtimeResult << ", " << compileTimeResult << std::endl;
    return 0;
}

해결책: 컴파일 타임에 사용하려면 캡처하는 변수도 constexpr이어야 합니다.

문제 2: 재귀 람다

#include <functional>
#include <iostream>

// ❌ 람다 재귀 (C++17)
// auto factorial =  {
//     return n <= 1 ? 1 : n * factorial(n - 1);  // 에러: factorial 미정의
// };

// ✅ std::function 사용
std::function<int(int)> factorial = [&](int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
};

// ✅ C++23: 명시적 this (Deducing this)
// auto factorial =  {
//     return n <= 1 ? 1 : n * self(n - 1);
// };

int main() {
    std::cout << "5! = " << factorial(5) << std::endl;  // 120
    return 0;
}

해결책: C++17에서는 std::function을 사용하거나, C++23의 명시적 this를 사용하세요.

문제 3: static_assert 실패

constexpr auto divide =  {
    return a / b;
};

// ❌ 0으로 나누기
// static_assert(divide(10, 0) == 0);  // 컴파일 에러!

// ✅ 조건 검사
constexpr auto safeDivide =  {
    return (b != 0) ? a / b : 0;
};

static_assert(safeDivide(10, 0) == 0);  // OK
static_assert(safeDivide(10, 2) == 5);  // OK

문제 4: 타입 추론 혼동

#include <iostream>

constexpr auto add =  {
    return a + b;
};

int main() {
    // 타입이 다르면 결과 타입도 달라짐
    constexpr int r1 = add(1, 2);        // int + int = int
    constexpr double r2 = add(1.5, 2.5); // double + double = double
    constexpr double r3 = add(1, 2.5);   // int + double = double
    
    static_assert(r1 == 3);
    static_assert(r2 == 4.0);
    static_assert(r3 == 3.5);
    
    std::cout << r1 << ", " << r2 << ", " << r3 << std::endl;
    return 0;
}

6. 활용 패턴

패턴 1: 컴파일 타임 검증

#include <type_traits>

// 범위 검증
constexpr auto inRange =  {
    return value >= min && value <= max;
};

static_assert(inRange(50, 0, 100));
// static_assert(inRange(150, 0, 100));  // 컴파일 에러!

// 타입 검증
constexpr auto isNumeric =  {
    using T = decltype(value);
    return std::is_arithmetic_v<T>;
};

static_assert(isNumeric(10));
static_assert(isNumeric(3.14));
// static_assert(isNumeric("text"));  // 컴파일 에러!

패턴 2: 컴파일 타임 룩업 테이블

#include <array>
#include <iostream>

template<size_t N>
constexpr auto makeLookupTable =  {
    std::array<int, N> table{};
    for (size_t i = 0; i < N; i++) {
        table[i] = i * i * i;  // 세제곱
    }
    return table;
};

constexpr auto cubes = makeLookupTable<10>();

int main() {
    std::cout << "5^3 = " << cubes[5] << std::endl;  // 125
    return 0;
}

패턴 3: 타입 변환 유틸리티

#include <type_traits>
#include <iostream>

constexpr auto toInt =  {
    return static_cast<int>(value);
};

constexpr auto toDouble =  {
    return static_cast<double>(value);
};

int main() {
    constexpr int i = toInt(3.14);      // 3
    constexpr double d = toDouble(10);  // 10.0
    
    static_assert(i == 3);
    static_assert(d == 10.0);
    
    std::cout << i << ", " << d << std::endl;
    return 0;
}

7. 실전 예제: 컴파일 타임 수학 라이브러리

#include <iostream>
#include <array>
#include <cmath>

namespace CompileTimeMath {
    // 거듭제곱
    constexpr auto pow =  {
        int result = 1;
        for (int i = 0; i < exp; i++) {
            result *= base;
        }
        return result;
    };
    
    // Factorial
    constexpr auto factorial =  {
        int result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    };
    
    // 피보나치
    constexpr auto fibonacci =  {
        if (n <= 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    };
    
    // 소수 판별
    constexpr auto isPrime =  {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    };
    
    // 최대공약수 (GCD)
    constexpr auto gcd =  {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    };
}

int main() {
    using namespace CompileTimeMath;
    
    // 모두 컴파일 타임에 계산됨
    constexpr int p = pow(2, 10);           // 1024
    constexpr int f = factorial(6);         // 720
    constexpr int fib = fibonacci(10);      // 55
    constexpr bool prime = isPrime(17);     // true
    constexpr int g = gcd(48, 18);          // 6
    
    static_assert(p == 1024);
    static_assert(f == 720);
    static_assert(fib == 55);
    static_assert(prime);
    static_assert(g == 6);
    
    std::cout << "2^10 = " << p << std::endl;
    std::cout << "6! = " << f << std::endl;
    std::cout << "fib(10) = " << fib << std::endl;
    std::cout << "17은 소수? " << (prime ? "예" : "아니오") << std::endl;
    std::cout << "gcd(48, 18) = " << g << std::endl;
    
    return 0;
}

정리

핵심 요약

  1. C++17: 람다가 암시적으로 constexpr
  2. 컴파일 타임: 상수 표현식에서 사용 가능
  3. 런타임: 일반 람다처럼 사용 가능
  4. 캡처: constexpr 변수만 컴파일 타임 사용
  5. 제약: static 변수, 예외, 비constexpr 함수 호출 불가
  6. 재귀: std::function 또는 C++23 명시적 this

constexpr 람다 vs 일반 람다

특징constexpr 람다일반 람다
컴파일 타임 실행
런타임 실행
static 변수
예외
성능런타임 비용 없음런타임 실행
타입 안전성컴파일 타임 검증런타임 검증

실전 팁

사용 시기:

  • 컴파일 타임 상수 계산 (배열 크기, 템플릿 인자)
  • 타입 검증 및 메타프로그래밍
  • 룩업 테이블 생성
  • static_assert로 조건 검증

성능:

  • 컴파일 타임 계산으로 런타임 비용 제거
  • 복잡한 계산은 컴파일 시간 증가 가능
  • 적절한 균형 유지

주의사항:

  • 비constexpr 변수 캡처 시 컴파일 타임 사용 불가
  • 재귀는 std::function 필요 (C++17)
  • 예외, static 변수 사용 불가

다음 단계

  • C++ constexpr 함수
  • C++ if constexpr
  • C++ Template Lambda

관련 글

  • C++20 consteval 완벽 가이드 | 컴파일 타임 전용 함수
  • C++ constexpr 함수 |
  • C++ if constexpr |
  • C++ any |
  • 모던 C++ (C++11~C++20) 핵심 문법 치트시트 | 현업에서 자주 쓰는 한눈에 보기