[2026] C++20 Concepts Complete Guide | A New Era of Template Constraints
이 글의 핵심
C++20 concepts and requires: template constraints, standard concepts, custom concepts, requires expressions, and migration from SFINAE.
What are C++20 concepts? Why do we need them?
Problem scenario: template error message overload
Problem: If you pass the wrong type to a template function, the error message will be hundreds of lines long. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
add("hello", "world");
// Error: no operator+ for const char*
// Dozens of lines of instantiation errors...
}
Solution: Concepts specifies constraints in template arguments, so that if an incorrect type is entered, an immediate clear error is issued. 다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template<Addable T>
T add(T a, T b) {
return a + b;
}
int main() {
add("hello", "world");
// Error: const char* does not satisfy Addable
// Clear and short error message!
}
아래 코드는 mermaid를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TD
subgraph before[Before C++17]
call1["add(string, string)"]
inst1["Template instantiation"]
err1["Long error message"]
end
subgraph after[C++20 concepts]
call2["add(string, string)"]
check["Concept check"]
err2["Clear error: Addable not satisfied"]
end
call1 --> inst1 --> err1
call2 --> check --> err2
Table of contents
- Basic syntax: concept, requires
- Standard concepts
- Authoring custom concepts
- Requires expressions
- Combining concepts
- Common problems and fixes
- Production patterns
- Complete example: generic algorithm
- SFINAE vs concepts
- Migration guide
1. Basic syntax: concept and requires
Defining a concept
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <concepts>
// Basic form
template<typename T>
concept MyConstraint = /* boolean expression */;
// Example: addable type
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
Using concepts
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Option 1: template<Concept T>
template<Addable T>
T add(T a, T b) {
return a + b;
}
// Option 2: requires clause
template<typename T>
requires Addable<T>
T add(T a, T b) {
return a + b;
}
// Option 3: Trailing requires
template<typename T>
T add(T a, T b) requires Addable<T> {
return a + b;
}
// Option 4: auto (abbreviated function template)
auto add(Addable auto a, Addable auto b) {
return a + b;
}
2. Standard concepts
Type classification
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <concepts>
// Integer types
template<std::integral T>
T square(T x) {
return x * x;
}
// Floating-point types
template<std::floating_point T>
T sqrt_approx(T x) {
return x / 2;
}
// Signed integers
template<std::signed_integral T>
T negate(T x) {
return -x;
}
// Unsigned integers
template<std::unsigned_integral T>
T increment(T x) {
return x + 1;
}
int main() {
square(5); // OK: int
sqrt_approx(9.0); // OK: double
negate(-10); // OK: int
increment(10u); // OK: unsigned int
}
Relationship concepts
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Same type
template<typename T, typename U>
requires std::same_as<T, U>
void func(T a, U b) {
// T and U are the same type
}
// Convertible
template<typename From, typename To>
requires std::convertible_to<From, To>
To convert(From value) {
return static_cast<To>(value);
}
// Derived-from relationship
template<typename Derived, typename Base>
requires std::derived_from<Derived, Base>
void process(Derived* ptr) {
Base* base = ptr; // OK
}
Comparison concepts
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Equality comparable
template<std::equality_comparable T>
bool is_equal(T a, T b) {
return a == b;
}
// Totally ordered
template<std::totally_ordered T>
T max(T a, T b) {
return (a > b) ? a : b;
}
Invocable concepts
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Invocable
template<typename F, typename....Args>
requires std::invocable<F, Args...>
auto call(F func, Args....args) {
return func(args...);
}
// Predicate (returns bool)
template<typename F, typename T>
requires std::predicate<F, T>
bool test(F pred, T value) {
return pred(value);
}
Object concepts
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Default constructible
template<std::default_initializable T>
T create() {
return T{};
}
// Copy constructible
template<std::copy_constructible T>
T duplicate(const T& value) {
return T(value);
}
// Move constructible
template<std::move_constructible T>
T transfer(T&& value) {
return T(std::move(value));
}
3. Custom concepts
Container Concept
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept Container = requires(T c) {
// Type members
typename T::value_type;
typename T::iterator;
// Member functions
{ c.size() } -> std::same_as<std::size_t>;
{ c.begin() } -> std::same_as<typename T::iterator>;
{ c.end() } -> std::same_as<typename T::iterator>;
{ c.empty() } -> std::convertible_to<bool>;
};
template<Container C>
void print_size(const C& container) {
std::cout << "Size: " << container.size() << '\n';
}
int main() {
std::vector<int> v = {1, 2, 3};
print_size(v); // OK
int arr[] = {1, 2, 3};
// print_size(arr); // Error: int[] not Container
}
Serializable Concept
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept Serializable = requires(T obj, std::ostream& os, std::istream& is) {
{ obj.serialize(os) } -> std::same_as<void>;
{ T::deserialize(is) } -> std::same_as<T>;
};
template<Serializable T>
void save(const T& obj, std::ostream& os) {
obj.serialize(os);
}
template<Serializable T>
T load(std::istream& is) {
return T::deserialize(is);
}
Numeric Concept
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T abs(T value) {
return value < 0 ? -value : value;
}
template<Numeric T>
T clamp(T value, T min, T max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
4. Requires expressions
Simple requirements
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
concept HasSize = requires(T t) {
t.size(); // member size() exists
};
Type requirements
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
concept HasValueType = requires {
typename T::value_type; // nested type value_type exists
};
Compound requirements
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
{ a > b } -> std::convertible_to<bool>;
{ a == b } -> std::convertible_to<bool>;
};
Nested requirements
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept ComplexConstraint = requires(T t) {
// Simple requirement
t.method();
// Type requirement
typename T::value_type;
// Compound requirement
{ t.size() } -> std::same_as<std::size_t>;
// Nested requirement
requires std::default_initializable<T>;
requires sizeof(T) <= 64;
};
5. Combining concepts
Logical combinations
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// AND
template<typename T>
concept SignedIntegral = std::integral<T> && std::signed_integral<T>;
// OR
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// NOT (use requires clause for negation)
template<typename T>
requires (!std::integral<T>)
void func(T value);
Concept layering
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept Movable = std::move_constructible<T> && std::movable<T>;
template<typename T>
concept Copyable = Movable<T> && std::copy_constructible<T>;
template<typename T>
concept Semiregular = Copyable<T> && std::default_initializable<T>;
template<typename T>
concept Regular = Semiregular<T> && std::equality_comparable<T>;
6. Common problems and fixes
Problem 1: Constraint violation
Symptom: error: no matching function ....constraints not satisfied.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 실행 예제
template<std::integral T>
T square(T x) {
return x * x;
}
int main() {
// square(3.14); // Error: double does not satisfy std::integral
}
Solution: Pass the correct type or relax the Concept. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
requires std::integral<T> || std::floating_point<T>
T square(T x) {
return x * x;
}
int main() {
square(5); // OK
square(3.14); // OK
}
Problem 2: Circular concepts
Cause: Concept A references B and B references A. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Circular
template<typename T>
concept ConceptA = ConceptB<T>;
template<typename T>
concept ConceptB = ConceptA<T>;
// Correct definition
template<typename T>
concept ConceptA = std::integral<T>;
template<typename T>
concept ConceptB = ConceptA<T> && std::signed_integral<T>;
Problem 3: Requires expression fails
Cause: If an expression in requires fails to compile, the concept is false.
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T>
concept HasFoo = requires(T t) {
t.foo(); // false if foo() is missing
};
struct A {};
struct B { void foo(); };
static_assert(!HasFoo<A>); // OK
static_assert(HasFoo<B>); // OK
7. Production patterns
Pattern 1: Concept-based overloading
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <concepts>
#include <iostream>
// Integral
template<std::integral T>
void print(T value) {
std::cout << "Integer: " << value << '\n';
}
// Floating-point
template<std::floating_point T>
void print(T value) {
std::cout << "Float: " << value << '\n';
}
// String
void print(const std::string& value) {
std::cout << "String: " << value << '\n';
}
int main() {
print(42); // Integer: 42
print(3.14); // Float: 3.14
print("hello"s); // String: hello
}
Pattern 2: Constraint hierarchy
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Base
template<typename T>
concept Drawable = requires(T t) {
t.draw();
};
// With color
template<typename T>
concept ColoredDrawable = Drawable<T> && requires(T t) {
t.setColor(0, 0, 0);
};
// With animation
template<typename T>
concept AnimatedDrawable = ColoredDrawable<T> && requires(T t) {
t.animate();
};
// Overloads
void render(Drawable auto& obj) {
obj.draw();
}
void render(ColoredDrawable auto& obj) {
obj.setColor(255, 0, 0);
obj.draw();
}
void render(AnimatedDrawable auto& obj) {
obj.animate();
obj.draw();
}
Pattern 3: Range concept
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename R>
concept Range = requires(R r) {
std::ranges::begin(r);
std::ranges::end(r);
};
template<Range R>
void process(R&& range) {
for (auto&& elem : range) {
// process
}
}
8. Complete example: generic algorithm
Sortable container
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <concepts>
#include <vector>
#include <algorithm>
#include <iostream>
template<typename T>
concept Sortable = requires(T container) {
typename T::value_type;
{ container.begin() } -> std::same_as<typename T::iterator>;
{ container.end() } -> std::same_as<typename T::iterator>;
requires std::totally_ordered<typename T::value_type>;
};
template<Sortable C>
void sort_container(C& container) {
std::sort(container.begin(), container.end());
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5};
sort_container(v);
for (int x : v) {
std::cout << x << ' ';
}
// 1 1 3 4 5
}
9. SFINAE vs concepts
SFINAE (C++17)
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <type_traits>
// Integral
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
T square(T x) {
return x * x;
}
// Floating-point
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
T square(T x) {
return x * x;
}
Concepts (C++20)
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Integral
template<std::integral T>
T square(T x) {
return x * x;
}
// Floating-point
template<std::floating_point T>
T square(T x) {
return x * x;
}
Comparison:
| Aspect | SFINAE | Concepts |
|---|---|---|
| Readability | Lower | Higher |
| Error messages | Verbose | Clearer |
| Compile time | Often slower | Often faster |
| Overloading | Verbose | Simpler |
10. Migration guide
enable_if → concepts
Before (C++17): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
add(T a, T b) {
return a + b;
}
After (C++20): 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<std::integral T>
T add(T a, T b) {
return a + b;
}
SFINAE traits → concepts
Before: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T, typename = void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};
template<typename T>
std::enable_if_t<has_size<T>::value, std::size_t>
get_size(const T& container) {
return container.size();
}
After: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
concept HasSize = requires(T t) {
{ t.size() } -> std::convertible_to<std::size_t>;
};
template<HasSize T>
std::size_t get_size(const T& container) {
return container.size();
}
Summary
| Idea | Description |
|---|---|
| Concept | Named template constraint |
| requires | States constraints |
| Standard concepts | Provided in <concepts> |
| Custom concept | concept Name = requires { ....} |
| Overloading | Overloads selected by concepts |
| C++20 concepts clarify template errors, improve readability, and offer a modern alternative to much SFINAE. |
FAQ
Q1: Concepts vs SFINAE?
A: Concepts usually win on readability, diagnostics, and often compile time. Prefer them in C++20 and later.
Q2: What is a requires expression?
A: Inside requires(T t) { ....} you list what must compile: members, nested types, operators, etc.
Q3: Can auto and Concept be used together?
A: Yes—e.g. void f(std::integral auto x) as an abbreviated function template.
Q4: Can I combine concepts?
A: Yes—combine with && / || or build layers of named concepts.
Q5: What is compiler support?
A:
- GCC 10+: full support
- Clang 10+: full support
- MSVC 2019 (16.3+): full support
Q6: What are Concepts learning resources?
A:
- cppreference - Constraints and concepts
- “C++20: The Complete Guide” by Nicolai Josuttis
- “C++ Templates: The Complete Guide” 2nd Edition One-line summary: C++20 Concepts can clarify template constraints and improve errors. Next, you might want to read Coroutines.
Related reading (internal links)
Other posts that connect to this topic.
- Complete Guide to C++20 Modules | Beyond header files
- C++20 Coroutines Complete Guide | A new era in asynchronous programming
- C++ SFINAE | “Substitution Failure Is Not An Error” guide
- C++ enable_if | “Conditional Compilation” Guide
Practical tips
Tips you can apply at work.
Debugging
- When something breaks, check compiler warnings first
- Reproduce with a small test case
Performance
- Do not optimize without profiling
- Define measurable targets first
Code review
- Pre-check areas that often get flagged in review
- Follow team coding conventions
Production checklist
Things to verify when applying this idea in practice.
Before coding
- Is this technique the best fit for the problem?
- Can teammates understand and maintain it?
- Does it meet performance requirements?
While coding
- Are all compiler warnings addressed?
- Are edge cases considered?
- Is error handling appropriate?
At review
- Is intent clear?
- Are tests sufficient?
- Is it documented? Use this checklist to reduce mistakes and improve quality.