C++ constexpr Lambda | 'Compile-Time Lambda' Guide
이 글의 핵심
C++ constexpr Lambda: "Compile-Time Lambda" Guide. Constexpr lambda basics and compile-time calculation.
Introduction
C++17 constexpr lambda is a lambda expression executable at compile-time. Used for metaprogramming, compile-time calculation, type validation, etc., providing powerful features without runtime cost.
Reality in Production
When learning development, everything is clean and theoretical. But production is different. You wrestle with legacy code, chase tight deadlines, and face unexpected bugs. The content covered in this guide was initially learned as theory, but I realized “ah, that’s why it’s designed this way” while applying it to actual projects.
What stands out in my memory is the trial and error from my first project. I did it as I learned from books but spent days not knowing why it didn’t work. Eventually, I found the problem through a senior developer’s code review and learned a lot in the process. This guide covers not only theory but also pitfalls you may encounter in practice and their solutions.
1. constexpr Lambda Basics
C++17 Implicit constexpr
Here is detailed implementation code using C++. Import the necessary modules. Understand the role of each part while examining the code.
#include <iostream>
// C++17: lambda implicitly constexpr
auto add = [](int a, int b) {
return a + b;
};
int main() {
// Compile-time use
constexpr int result1 = add(3, 4); // 7
static_assert(add(3, 4) == 7);
// Runtime use also possible
int x = 10, y = 20;
int result2 = add(x, y); // 30
std::cout << result1 << ", " << result2 << std::endl;
return 0;
}
Key Concepts:
- From C++17, lambdas are implicitly constexpr
- Condition: lambda body satisfies
constexprrequirements - Usable at both compile-time and runtime
Explicit constexpr
Below is an implementation example using C++. Understand the role of each part while examining the code.
// Explicitly specify constexpr
constexpr auto square = [](int x) constexpr {
return x * x;
};
constexpr int result = square(5); // 25
static_assert(square(5) == 25);
// Use as array size
int arr[square(4)]; // Size 16
2. Compile-Time Calculation
Example 1: Factorial
Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.
#include <iostream>
constexpr auto factorial = [](int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
};
int main() {
// Compile-time calculation
constexpr int f5 = factorial(5); // 120
static_assert(factorial(5) == 120);
// Use as array size
int arr[factorial(4)]; // Size 24
std::cout << "5! = " << f5 << std::endl;
std::cout << "Array size: " << sizeof(arr) / sizeof(int) << std::endl;
return 0;
}
Example 2: Power (Using Template)
Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.
#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;
}
Example 3: Array Initialization
Here is detailed implementation code using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.
#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. Type Checking and Metaprogramming
Type Checking
Here is detailed implementation code using C++. Import the necessary modules. Understand the role of each part while examining the code.
#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 << "Type check passed" << std::endl;
return 0;
}
Conditional Compilation
Here is detailed implementation code using C++. Import the necessary modules and perform branching with conditionals. Understand the role of each part while examining the code.
#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. Constraints
What’s Allowed
Here is detailed implementation code using C++. Process data with loops. Understand the role of each part while examining the code.
// ✅ Allowed: basic operations
constexpr auto add = [](int a, int b) { return a + b; };
// ✅ Allowed: loops
constexpr auto sum = [](int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
};
// ✅ Allowed: recursion
constexpr auto fibonacci = [](int n) {
auto fib = [](int n, auto& self) -> int {
if (n <= 1) return n;
return self(n - 1, self) + self(n - 2, self);
};
return fib(n, fib);
};
// ✅ Allowed: capture (C++17)
constexpr int x = 10;
constexpr auto addX = [x](int y) { return x + y; };
What’s Not Allowed
Here is the nonConstexpr implementation:
// ❌ Not allowed: non-constexpr function call
int nonConstexpr(int x) { return x * 2; }
constexpr auto bad1 = [](int x) {
return nonConstexpr(x); // Error
};
// ❌ Not allowed: static variable (C++17)
constexpr auto bad2 = []() {
static int count = 0; // Error
return count++;
};
// ❌ Not allowed: dynamic allocation
constexpr auto bad3 = []() {
int* p = new int(10); // Error
delete p;
return 0;
};
// ❌ Not allowed: I/O
constexpr auto bad4 = []() {
std::cout << "Hello"; // Error
return 0;
};
Summary
Key Points
- C++17 constexpr lambda: Implicitly constexpr
- Compile-time calculation: No runtime cost
- Type checking: Powerful with type_traits
- Metaprogramming: Template + lambda combination
- Constraints: No I/O, dynamic allocation
When to Use
✅ Use constexpr lambda when:
- Need compile-time calculation
- Metaprogramming
- Type validation
- Performance optimization
❌ Don’t use when:
- Need I/O operations
- Dynamic allocation required
- Too complex (readability matters)
Best Practices
- ✅ Use for compile-time calculation
- ✅ Combine with templates
- ✅ Use for type checking
- ❌ Don’t overuse (readability first)
- ❌ Don’t use for side effects
Related Articles
- C++ Compile-Time Programming
- C++ constexpr Function
- C++ if constexpr
Master compile-time programming with constexpr lambda! 🚀