C++ constexpr Lambda | '컴파일 타임 람다' 가이드
이 글의 핵심
C++ constexpr Lambda: "컴파일 타임 람다" 가이드. constexpr 람다 기본·컴파일 타임 계산.
들어가며
C++17 constexpr 람다는 컴파일 타임에 실행 가능한 람다 표현식입니다. 메타프로그래밍, 컴파일 타임 계산, 타입 검증 등에 활용되며, 런타임 비용 없이 강력한 기능을 제공합니다.
1. constexpr 람다 기본
constexpr 함수는 C++11부터 있었지만, 람다 표현식이 컴파일 타임에 자연스럽게 녹아든 것은 C++17부터입니다. 그전까지는 람다로 계산 로직을 짜 놓고도 정작 배열 크기나 템플릿 인자처럼 컴파일 타임 상수가 필요한 자리에는 쓸 수 없어, 똑같은 로직을 constexpr 함수로 따로 만들어야 하는 중복이 발생했습니다. C++17은 이 간극을 없애고, 람다가 특정 조건만 만족하면 별도의 키워드 없이도 컴파일 타임 실행이 가능하도록 규칙을 바꿨습니다.
C++17 암시적 constexpr
핵심 규칙은 간단합니다. 람다 본문이 constexpr 함수가 지켜야 하는 요구사항(예외를 던지지 않고, static/thread_local 변수를 쓰지 않으며, 모든 호출 대상이 constexpr이어야 한다는 등)을 만족하기만 하면, 별도로 constexpr 키워드를 붙이지 않아도 컴파일러가 암시적으로 해당 람다를 constexpr로 취급합니다. 아래 add 람다는 이 조건을 만족하므로 static_assert와 배열 크기 지정 같은 컴파일 타임 문맥에서 그대로 사용할 수 있고, 동시에 런타임 변수를 넘겨 일반 함수처럼 호출하는 것도 가능합니다.
#include <iostream>
// C++17: 람하다가 암시적으로 constexpr
auto add = [](int a, int b) {
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 키워드를 직접 붙일 수 있습니다. 특히 팀 단위 코드베이스에서는 “이 람다는 컴파일 타임에도 쓰일 수 있다”는 것을 리뷰어와 미래의 자신에게 알리는 문서화 효과가 있어, 명시적으로 적어두는 스타일을 권장하는 곳이 많습니다. 아래 square 람다처럼 명시적으로 선언해두면, 배열 크기 지정처럼 반드시 컴파일 타임 상수가 필요한 자리에서도 안심하고 사용할 수 있습니다.
// 명시적으로 constexpr 지정
constexpr auto square = [](int x) constexpr {
return x * x;
};
constexpr int result = square(5); // 25
static_assert(square(5) == 25);
// 배열 크기로 사용
int arr[square(4)]; // 크기 16
2. 컴파일 타임 계산
constexpr 람다가 실제로 빛을 발하는 곳은 반복적인 수치 계산을 컴파일 타임으로 옮길 때입니다. 팩토리얼, 거듭제곱, 배열 초기화처럼 입력이 고정되어 있고 계산 자체가 순수 함수인 경우, 굳이 프로그램 실행 시점까지 계산을 미룰 이유가 없습니다. 아래 세 예제를 통해 반복문을 포함한 람다도 컴파일 타임에 완전히 평가될 수 있다는 것을 확인해 보겠습니다.
예제 1: Factorial
factorial 람다는 내부에 for 루프를 포함하고 있지만, C++14부터 constexpr 함수 안에서 반복문이 허용되었기 때문에 문제없이 컴파일 타임 요구사항을 만족합니다. constexpr int f5 = factorial(5)처럼 결과를 constexpr 변수에 담으면 컴파일러가 이 계산을 컴파일 시점에 완전히 수행해 버리므로, 런타임에는 이미 계산된 상수 120만 남습니다. 이 값을 그대로 배열 크기로 사용하는 것도 가능합니다.
#include <iostream>
constexpr auto factorial = [](int n) {
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: 거듭제곱 (템플릿 활용)
템플릿 비타입 매개변수(non-type template parameter)와 constexpr 람다를 결합하면, 지수처럼 컴파일 타임에 고정되는 값을 템플릿 인자로 분리하고 밑(base)만 런타임 인자로 받는 구조를 만들 수 있습니다. 아래 power<N>은 지수 N을 템플릿 인자로 고정한 뒤 base만 넘겨받는 형태로, power<3>(2)와 power<5>(3)처럼 서로 다른 지수에 대해 별도의 람다 인스턴스가 생성됩니다. 이런 패턴은 고정된 차수의 다항식 계산이나 단위 변환 계수처럼, 지수는 자주 바뀌지 않고 밑만 다양한 상황에서 유용합니다.
#include <iostream>
template<int N>
constexpr auto power = [](int base) {
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: 배열 초기화
std::array와 constexpr 람다를 조합하면 프로그램 시작 전에 이미 값이 채워진 룩업 테이블을 만들 수 있습니다. 아래 makeArray<N>은 크기 N짜리 배열을 만들고 각 인덱스의 제곱값을 채워 넣는데, 이 전체 과정이 컴파일 타임에 일어나므로 런타임에는 반복문을 한 번도 돌지 않고 이미 완성된 배열만 존재하게 됩니다. 런타임 초기화 비용이 부담스러운 임베디드 환경이나, 시작 시간이 중요한 애플리케이션에서 특히 유용한 패턴입니다.
#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. 타입 검사 및 메타프로그래밍
constexpr 람다는 <type_traits>와 결합했을 때 컴파일 타임 메타프로그래밍의 강력한 도구가 됩니다. 제네릭 람다(auto 매개변수)와 decltype을 함께 쓰면, 별도의 클래스 템플릿이나 특수화 없이도 타입에 따라 다른 동작을 결정하는 코드를 간결하게 표현할 수 있습니다. 이 절에서는 타입을 검사하는 람다와, 타입에 따라 분기하는 람다를 각각 살펴봅니다.
타입 검사
isIntegral과 isFloating은 std::is_integral_v, std::is_floating_point_v를 감싼 얇은 래퍼로, 인자로 전달된 값의 타입을 decltype으로 추출해 판별합니다. static_assert와 함께 쓰면 “이 값은 반드시 정수여야 한다”는 제약을 컴파일 타임에 강제할 수 있어, 잘못된 타입이 함수에 들어오는 것을 런타임까지 기다리지 않고 빌드 단계에서 바로 잡아낼 수 있습니다.
#include <type_traits>
#include <iostream>
constexpr auto isIntegral = [](auto value) {
return std::is_integral_v<decltype(value)>;
};
constexpr auto isFloating = [](auto value) {
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;
}
조건부 컴파일
if constexpr은 컴파일 타임에 조건을 평가해서 선택되지 않은 분기를 아예 컴파일 대상에서 제외해 버리는 문법으로, 제네릭 람다 안에서 타입별로 완전히 다른 코드를 실행하고 싶을 때 필수적입니다. 아래 processValue는 정수형이면 2를 곱하고 부동소수점이면 1.5를 곱하는데, 만약 일반 if를 썼다면 두 분기 모두 모든 타입에 대해 컴파일이 가능해야 하므로 타입 불일치 오류가 발생했을 것입니다. if constexpr 덕분에 실제로 인스턴스화되는 타입에 맞는 분기만 컴파일되어 이런 문제가 사라집니다.
#include <type_traits>
#include <iostream>
constexpr auto processValue = [](auto value) {
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 람다가 아무 코드나 담을 수 있는 것은 아닙니다. 표준은 컴파일 타임에 실행이 가능하도록 상당히 엄격한 규칙을 두고 있으며, 이 규칙을 모르고 코드를 작성하면 “왜 이 람다는 컴파일 타임에 안 되지?”라는 의문에 부딪히게 됩니다. 아래에서 허용되는 패턴과 금지되는 패턴을 나란히 비교합니다.
허용되는 것
기본 산술 연산, 삼항 연산자나 if/for 같은 제어문, 그리고 constexpr로 선언된 변수의 캡처는 모두 문제없이 컴파일 타임 평가가 가능합니다. 아래 코드는 실무에서 자주 쓰이는 네 가지 패턴 — 덧셈, 최댓값 계산, 합계 누적, 상수 캡처 — 이 전부 constexpr 람다의 규칙 안에 들어온다는 것을 보여줍니다.
// ✅ 허용: 기본 연산
constexpr auto add = [](auto x, auto y) {
return x + y;
};
// ✅ 허용: 제어문
constexpr auto max = [](auto a, auto b) {
return (a > b) ? a : b;
};
// ✅ 허용: 루프
constexpr auto sum = [](int n) {
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 함수 호출, 그리고 컴파일 타임에 값이 확정되지 않은 변수의 캡처는 모두 컴파일 타임 평가를 불가능하게 만듭니다. 이런 코드를 컴파일 타임 문맥(static_assert, constexpr 변수 초기화, 배열 크기 등)에서 사용하려고 하면 컴파일러가 명확한 오류를 내며 거부하므로, 런타임 전용 로직과 컴파일 타임 로직을 섞어 쓸 때는 이 경계를 미리 파악해두는 것이 중요합니다.
// ❌ 불가능: static 지역 변수
constexpr auto invalid1 = [](auto x) {
static int count = 0; // 에러!
return x;
};
// ❌ 불가능: 예외 던지기
constexpr auto invalid2 = [](auto x) {
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. 자주 발생하는 문제
constexpr 람다를 쓰다 보면 “런타임에서는 잘 되는데 컴파일 타임에서만 실패한다”는 식의 혼란스러운 오류를 자주 마주치게 됩니다. 아래 네 가지는 실무에서 특히 자주 반복되는 문제 유형으로, 각각의 원인과 해결책을 함께 정리했습니다.
문제 1: 비constexpr 변수 캡처
람다 본문 자체는 constexpr 조건을 만족하더라도, 캡처한 변수가 컴파일 타임 상수가 아니라면 그 람다 호출 결과 역시 컴파일 타임 상수가 될 수 없습니다. 아래 예제에서 x는 일반 int 변수이므로 lambda1을 런타임 함수처럼 호출하는 것은 문제없지만, 그 결과를 constexpr 변수에 담으려는 시도는 컴파일 오류로 이어집니다. 반면 y처럼 애초에 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: 재귀 람다
람다는 자기 자신을 가리키는 이름이 본문 안에서 아직 존재하지 않기 때문에, auto로 선언한 람다 안에서 자기 자신을 직접 호출하는 재귀는 C++17까지는 불가능합니다. 흔히 쓰는 우회법은 std::function으로 타입을 먼저 고정한 뒤 참조 캡처([&])로 자기 자신을 캡처하는 것이지만, 이 방식은 std::function의 타입 소거로 인한 약간의 런타임 오버헤드를 감수해야 합니다. C++23부터는 “명시적 this”(Deducing this) 문법으로 이 문제를 오버헤드 없이 해결할 수 있습니다.
#include <functional>
#include <iostream>
// ❌ 람다 재귀 (C++17)
// auto factorial = [](int n) {
// 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 = [](this auto self, int n) {
// 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 실패
컴파일 타임 계산 중 0으로 나누기처럼 정의되지 않은 동작이 발생하면, 런타임과 달리 프로그램이 조용히 이상한 값을 내는 대신 컴파일 자체가 실패합니다. 이는 버그를 훨씬 이른 단계에서 잡아낸다는 점에서 오히려 장점이지만, 처음 겪으면 왜 static_assert 한 줄 때문에 빌드가 깨지는지 당황스러울 수 있습니다. 아래처럼 나누기 전에 분모가 0인지 검사하는 방어 로직을 람다 안에 넣어두면, 컴파일 타임과 런타임 모두에서 안전하게 동작합니다.
constexpr auto divide = [](int a, int b) {
return a / b;
};
// ❌ 0으로 나누기
// static_assert(divide(10, 0) == 0); // 컴파일 에러!
// ✅ 조건 검사
constexpr auto safeDivide = [](int a, int b) {
return (b != 0) ? a / b : 0;
};
static_assert(safeDivide(10, 0) == 0); // OK
static_assert(safeDivide(10, 2) == 5); // OK
문제 4: 타입 추론 혼동
제네릭 람다(auto 매개변수)는 실제로 전달되는 인자의 타입에 따라 반환 타입이 달라진다는 점을 놓치기 쉽습니다. 아래 add(a, b)는 a + b의 결과 타입을 그대로 반환하므로, 두 정수를 넘기면 int가, 하나라도 double이 섞이면 통상적인 산술 변환 규칙에 따라 double이 반환됩니다. constexpr int r3 = add(1, 2.5)처럼 반환 타입과 다른 타입의 변수에 결과를 담으려 하면 암묵적 변환이 일어나므로, 의도한 타입과 실제 계산 타입이 일치하는지 항상 확인하는 습관이 필요합니다.
#include <iostream>
constexpr auto add = [](auto a, auto b) {
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: 컴파일 타임 검증
inRange나 isNumeric처럼 간단한 조건을 검사하는 람다를 static_assert와 함께 쓰면, 상수로 주어지는 설정값이나 매직 넘버가 유효한 범위인지 컴파일 타임에 강제할 수 있습니다. 런타임 검증과 달리 이런 검사는 프로그램이 배포되기도 전에, 심지어 CI 빌드 단계에서 실패하기 때문에 잘못된 상수가 프로덕션에 나가는 것을 원천 차단합니다.
#include <type_traits>
// 범위 검증
constexpr auto inRange = [](int value, int min, int max) {
return value >= min && value <= max;
};
static_assert(inRange(50, 0, 100));
// static_assert(inRange(150, 0, 100)); // 컴파일 에러!
// 타입 검증
constexpr auto isNumeric = [](auto value) {
using T = decltype(value);
return std::is_arithmetic_v<T>;
};
static_assert(isNumeric(10));
static_assert(isNumeric(3.14));
// static_assert(isNumeric("text")); // 컴파일 에러!
패턴 2: 컴파일 타임 룩업 테이블
세제곱 값이나 삼각함수 근사치처럼 계산 비용은 있지만 입력 범위가 제한적인 값들은, 실행할 때마다 다시 계산하는 대신 프로그램이 시작하기 전에 이미 채워진 테이블을 참조하는 방식이 훨씬 효율적입니다. 아래 makeLookupTable<N>은 정확히 이 패턴을 구현한 것으로, cubes[5]처럼 배열 인덱싱만으로 값을 즉시 얻을 수 있어 런타임에는 곱셈 연산조차 발생하지 않습니다.
#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: 타입 변환 유틸리티
static_cast를 감싸는 짧은 람다를 만들어두면, 타입 변환 의도를 이름으로 드러내면서도 컴파일 타임 상수에 대해서는 변환 자체가 컴파일 시점에 끝나버리는 이점을 얻을 수 있습니다. 아래 toInt, toDouble처럼 단순해 보이는 래퍼라도, 코드베이스 전체에서 일관된 변환 방식을 강제하고 싶을 때는 이런 작은 유틸리티 람다를 모아두는 것이 유지보수에 도움이 됩니다.
#include <type_traits>
#include <iostream>
constexpr auto toInt = [](auto value) {
return static_cast<int>(value);
};
constexpr auto toDouble = [](auto value) {
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. 실전 예제: 컴파일 타임 수학 라이브러리
지금까지 다룬 개념을 종합해, 거듭제곱·팩토리얼·피보나치·소수 판별·최대공약수를 모두 컴파일 타임에 계산하는 작은 수학 라이브러리를 네임스페이스 하나에 모아 보겠습니다. 이런 형태로 유틸리티를 묶어두면 컴파일 타임 상수가 필요한 여러 곳에서 재사용하기 쉽고, 각 함수가 실제로 constexpr 조건을 만족하는지도 static_assert 한 줄로 즉시 검증할 수 있습니다.
#include <iostream>
#include <array>
#include <cmath>
namespace CompileTimeMath {
// 거듭제곱
constexpr auto pow = [](int base, int exp) {
int result = 1;
for (int i = 0; i < exp; i++) {
result *= base;
}
return result;
};
// Factorial
constexpr auto factorial = [](int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
};
// 피보나치
constexpr auto fibonacci = [](int n) {
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 = [](int n) {
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 = [](int a, int b) {
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;
}
정리
constexpr 람다는 C++17이 가져온 조용하지만 실질적인 개선점입니다. 별도의 문법을 새로 배울 필요 없이, 기존에 쓰던 람다가 특정 조건만 만족하면 자동으로 컴파일 타임 세계에 편입되기 때문입니다. 아래 요약과 비교표로 지금까지의 내용을 정리합니다.
핵심 요약
- C++17: 람하다가 암시적으로
constexpr - 컴파일 타임: 상수 표현식에서 사용 가능
- 런타임: 일반 람다처럼 사용 가능
- 캡처:
constexpr변수만 컴파일 타임 사용 - 제약: static 변수, 예외, 비constexpr 함수 호출 불가
- 재귀:
std::function또는 C++23 명시적this
constexpr 람다 vs 일반 람다
| 특징 | constexpr 람다 | 일반 람다 |
|---|---|---|
| 컴파일 타임 실행 | ✓ | ✗ |
| 런타임 실행 | ✓ | ✓ |
| static 변수 | ✗ | ✓ |
| 예외 | ✗ | ✓ |
| 성능 | 런타임 비용 없음 | 런타임 실행 |
| 타입 안전성 | 컴파일 타임 검증 | 런타임 검증 |
실전 팁
사용 시기:
- 컴파일 타임 상수 계산 (배열 크기, 템플릿 인자)
- 타입 검증 및 메타프로그래밍
- 룩업 테이블 생성
static_assert로 조건 검증
성능:
- 컴파일 타임 계산으로 런타임 비용 제거
- 복잡한 계산은 컴파일 시간 증가 가능
- 적절한 균형 유지
주의사항:
- 비constexpr 변수 캡처 시 컴파일 타임 사용 불가
- 재귀는
std::function필요 (C++17) - 예외, static 변수 사용 불가
다음 단계
관련 글
- C++20 consteval 완벽 가이드 | 컴파일 타임 전용 함수
- C++ constexpr 함수
- C++ if constexpr
- C++ any
- 모던 C++ (C++11~C++20) 핵심 문법 치트시트 | 현업에서 자주 쓰는 한눈에 보기
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Everything about C++ constexpr Lambda : from basic concepts to practical applications.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 컴파일 타임 프로그래밍 | constexpr·consteval·if constexpr 완벽 가이드
- C++ constexpr 함수 | ‘컴파일 타임 함수’ 가이드
- C++ if constexpr | ‘컴파일 타임 if’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, lambda, constexpr, C++17, compile-time 등으로 검색하시면 이 글이 도움이 됩니다.