[2026] C++ constexpr Lambda | 컴파일 타임 람다 가이드

[2026] C++ constexpr Lambda | 컴파일 타임 람다 가이드

이 글의 핵심

C++ constexpr Lambda: 컴파일 타임 람다 가이드. constexpr 람다 기본·컴파일 타임 계산.

들어가며

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

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. constexpr 람다 기본

C++17 암시적 constexpr

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 명시적으로 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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 거듭제곱 (템플릿 활용)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 배열 초기화

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 타입 검사 및 메타프로그래밍

타입 검사

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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;
}

조건부 컴파일

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 제약 사항

허용되는 것

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ✅ 허용: 기본 연산
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;
};

허용되지 않는 것

다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 불가능: 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 변수 캡처

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 재귀 람다

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 실패

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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: 타입 추론 혼동

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 컴파일 타임 검증

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 컴파일 타임 룩업 테이블

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 타입 변환 유틸리티

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 실전 예제: 컴파일 타임 수학 라이브러리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 변수 사용 불가

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3