[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;
}
정리
핵심 요약
- C++17: 람다가 암시적으로
constexpr - 컴파일 타임: 상수 표현식에서 사용 가능
- 런타임: 일반 람다처럼 사용 가능
- 캡처:
constexpr변수만 컴파일 타임 사용 - 제약: static 변수, 예외, 비constexpr 함수 호출 불가
- 재귀:
std::function또는 C++23 명시적this
constexpr 람다 vs 일반 람다
| 특징 | constexpr 람다 | 일반 람다 |
|---|---|---|
| 컴파일 타임 실행 | ✓ | ✗ |
| 런타임 실행 | ✓ | ✓ |
| static 변수 | ✗ | ✓ |
| 예외 | ✗ | ✓ |
| 성능 | 런타임 비용 없음 | 런타임 실행 |
| 타입 안전성 | 컴파일 타임 검증 | 런타임 검증 |
실전 팁
사용 시기:
- 컴파일 타임 상수 계산 (배열 크기, 템플릿 인자)
- 타입 검증 및 메타프로그래밍
- 룩업 테이블 생성
static_assert로 조건 검증 성능:- 컴파일 타임 계산으로 런타임 비용 제거
- 복잡한 계산은 컴파일 시간 증가 가능
- 적절한 균형 유지 주의사항:
- 비constexpr 변수 캡처 시 컴파일 타임 사용 불가
- 재귀는
std::function필요 (C++17) - 예외, static 변수 사용 불가