[2026] C++20 Concepts Complete Guide | A New Era of Template Constraints

[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

  1. Basic syntax: concept, requires
  2. Standard concepts
  3. Authoring custom concepts
  4. Requires expressions
  5. Combining concepts
  6. Common problems and fixes
  7. Production patterns
  8. Complete example: generic algorithm
  9. SFINAE vs concepts
  10. 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:

AspectSFINAEConcepts
ReadabilityLowerHigher
Error messagesVerboseClearer
Compile timeOften slowerOften faster
OverloadingVerboseSimpler

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

IdeaDescription
ConceptNamed template constraint
requiresStates constraints
Standard conceptsProvided in <concepts>
Custom conceptconcept Name = requires { ....}
OverloadingOverloads 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.

Other posts that connect to this topic.

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.

Keywords covered

C++, concept, cpp20, template, constraint, requires to find this post.

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