/

|

C++20 concepts는 템플릿 인자가 만족해야 하는 요구를 이름으로 적는 제약입니다.

이 글은 cppreference의 constraints·concepts 문서를 2026-09-01 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.

C++20 concepts는 무엇입니까?

한 줄 답: 이 개념은 템플릿 인자가 만족해야 할 요구를 이름으로 묶은 컴파일 시점 조건이며, 템플릿 인터페이스의 일부가 됩니다.

클래스 템플릿, 함수 템플릿, 제네릭 람다를 포함한 템플릿 함수에는 템플릿 인자에 대한 제약을 연결할 수 있습니다. 이 제약은 여러 후보 가운데 적절한 오버로드나 템플릿 특수화를 선택하는 데 사용됩니다. 이름 있는 요구는 컴파일 시점에 평가되는 술어이므로, 선언만 읽어도 해당 템플릿이 어떤 인자를 기대하는지 드러납니다.

개념은 네임스페이스 범위에서 이름과 제약 식을 함께 정의합니다. 기본 형태는 template <typename T> concept 이름 = 제약식;이며, 이름을 식별자로 사용하면 요구가 만족될 때 true, 만족되지 않을 때 false가 되는 조건으로 취급됩니다.

다음 Hashable<concepts> 표준 라이브러리에 정의된 이름이 아니라, cppreference의 constraints 문서에 나오는 문서 예시 개념입니다.

template<typename T>
concept Hashable = requires(T a)
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};

같은 요구는 템플릿 매개변수와 함수 선언의 여러 위치에 연결할 수 있습니다. 아래 형태들은 같은 Hashable<T> 제약을 서로 다른 문법 위치에 적는 대응 예시이며, 한 함수에 모두 함께 쓰는 형태는 아닙니다.

  • 타입 제약: template<Hashable T> void f(T) {}
  • 템플릿 매개변수 목록 뒤의 requires 절: template<typename T> requires Hashable<T> void f(T) {}
  • 함수 선언자 끝의 requires 절: template<typename T> void f(T) requires Hashable<T> {}
  • 축약 함수 템플릿 매개변수: void f(Hashable auto) {}

타입 제약 표기에서는 문맥에서 추론된 타입이 개념의 첫 번째 인수로 암묵적으로 전달됩니다. 따라서 일반적인 개념 호출에서 Hashable<T>라고 적는 조건을 Hashable T처럼 줄여 쓸 수 있습니다.

개념 정의 자체에는 몇 가지 제한이 있습니다. 자기 자신을 재귀적으로 참조할 수 없고, 이미 다른 제약을 붙인 템플릿 매개변수로 개념을 제한할 수 없습니다. 개념의 명시적 인스턴스화, 명시적 특수화, 부분 특수화도 허용되지 않습니다.

requires는 어떻게 사용합니까?

한 줄 답: requires 절은 템플릿이나 함수에 조건을 붙이고, requires 식은 그 조건을 만들기 위한 유효성 검사를 표현합니다.

requires 절은 템플릿 매개변수 목록 바로 뒤에 놓거나 함수 선언자의 마지막에 둘 수 있습니다. 절 뒤에는 템플릿 인자와 관련된 상수 식이 와야 하며, 보통 이름 있는 개념이나 여러 개념의 논리 결합, 또는 requires 식을 적습니다. 함수 호출처럼 보이는 조건식은 필요한 경우 괄호로 묶어 하나의 식으로 표현해야 합니다.

다음에서 Addable은 설명을 위해 정의한 예시 개념 이름입니다. 첫 번째 선언의 requires (T x) { x + x; } 부분이 requires 식이고, 두 번째 선언에서 requires Addable<T> 부분이 그 식으로 만든 조건을 함수에 연결하는 requires 절입니다.

template<typename T>
concept Addable = requires (T x) {
    x + x;
};

template<typename T>
requires Addable<T>
T add(T a, T b) {
    return a + b;
}

같은 절은 함수 선언자 끝에도 template<typename T> T add(T a, T b) requires Addable<T> { return a + b; }처럼 둘 수 있습니다. requires true처럼 항상 참인 상수 식도 문법상 가능하지만, 실제 제약에는 의미가 드러나는 개념이나 유효성 검사를 사용합니다.

requires 식은 bool 타입의 prvalue를 만들며, requires { 요구 목록 } 또는 requires (지역 매개변수 목록) { 요구 목록 } 형태로 씁니다. 요구는 C++20에서 다음 네 종류로 나뉩니다.

  • 단순 요구: a + b;처럼 식이 유효한지만 확인합니다. 식은 실제로 평가하지 않습니다.
  • 타입 요구: typename T::inner;처럼 중첩 타입이나 타입 이름을 사용할 수 있는지 확인합니다.
  • 복합 요구: { x + 1 } -> std::same_as<int>;처럼 식의 유효성과 결과 타입에 대한 제약을 함께 확인합니다. { *x } -> std::convertible_to<typename T::inner>;처럼 결과가 특정 타입으로 변환 가능한지도 표현할 수 있습니다.
  • 중첩 요구: 지역 매개변수를 바탕으로 requires 뒤에 추가 조건을 적습니다.

식의 지역 매개변수는 표기와 유효성 검사에만 쓰이며 저장 공간이나 수명, 연결을 갖는 실제 객체가 아닙니다. 이 매개변수 목록에는 기본 인수를 둘 수 없고 말줄임표로 끝낼 수 없습니다.

표준 개념은 어디서 확인합니까?

한 줄 답: 표준 라이브러리의 이름 있는 요구는 <concepts> 헤더와 std 네임스페이스의 concepts library 문서에서 확인합니다.

이 라이브러리는 템플릿 인자를 컴파일 시점에 검증하고, 타입의 속성에 따라 함수 분기를 선택할 때 사용할 기본 개념을 제공합니다. 핵심 언어 개념은 <concepts> 헤더에 정의되며 std 네임스페이스에 있습니다.

자주 확인할 이름은 다음과 같습니다.

이름
std::integral정수형 타입입니다.
std::same_as<T, U>두 타입이 같은지 확인합니다.
std::convertible_to<From, To>FromTo로 암시적 변환 가능한지 확인합니다.
std::derived_from<Derived, Base>한 타입이 다른 타입에서 파생되었는지 확인합니다.
std::floating_point부동소수점 타입인지 확인합니다.

std::signed_integral, std::unsigned_integral처럼 정수의 성질을 더 좁히는 이름도 같은 문서에서 확인할 수 있습니다. 비교·객체·호출과 관련된 개념도 같은 표준 라이브러리 문서에 있으며, iterator·algorithms·ranges 라이브러리에는 추가 개념이 정의되어 있습니다.

헤더와 네임스페이스를 명시한 뒤 타입 제약을 매개변수에 바로 적을 수 있습니다.

#include <concepts>

template<std::integral T>
T twice(T value) {
    return value + value;
}

std::integral T는 문맥에서 추론된 T를 이 개념의 인수로 전달하는 타입 제약입니다. 따라서 호출에 사용한 타입이 정수형 요구를 만족할 때만 이 함수 템플릿이 후보가 됩니다.

표준 개념은 구문적 요구와 의미적 요구를 함께 가질 수 있습니다. 구문적 요구를 만족하면 개념이 satisfied 상태가 되고, 의미적 요구까지 실제로 지키면 modeled 상태가 되지만, 일반적으로 컴파일러가 직접 확인할 수 있는 범위는 구문적 요구입니다.

SFINAE 대신 이 제약을 사용하는 이유는 무엇입니까?

한 줄 답: 이 제약은 템플릿 인터페이스에 요구를 드러내고, 조건 위반을 인스턴스화 초기에 컴파일 시점에 진단해 오류의 원인을 따라가기 쉽게 합니다.

이전의 템플릿 필터링은 인자를 치환하는 과정에서 실패하는지에 의존하기도 했습니다. 여기서는 그 구현 방법을 다루기보다, 이름 있는 요구를 선언에 직접 연결했을 때 생기는 차이에 집중합니다.

조건을 만족하지 않는 템플릿은 인스턴스화 초기부터 걸러집니다. 그 결과 일반적인 연산 오류가 긴 후보 목록과 함께 나타나는 대신, 어떤 이름 있는 개념이 만족되지 않았는지와 해당 조건의 위치가 진단에 드러나기 쉬워집니다. 오류가 사라진다는 뜻은 아니지만, 실패한 요구를 템플릿 인터페이스와 연결해 읽을 수 있습니다.

이름 있는 요구는 템플릿의 인터페이스 일부가 되므로, 함수 본문을 읽기 전에 허용할 인자의 범위를 파악할 수 있습니다. 또한 여러 제약을 &&로 결합하면 왼쪽부터 확인하고, 왼쪽 조건이 만족되지 않을 때 오른쪽에 대한 치환을 시도하지 않습니다. 이 단락 평가가 즉시 문맥 밖에서 발생할 수 있는 불필요한 치환 실패를 피하는 데 도움을 주며, || 결합도 같은 방식으로 단락 평가됩니다.

제약의 이름은 단순히 특정 연산 하나가 존재하는지를 나열하기보다 숫자나 호출 가능한 대상처럼 코드가 다루려는 의미 범주를 나타내는 편이 적절합니다. 템플릿 제약과 별개로 C++ 언어 기능의 범위를 점검하는 참고 글은 C++ 기술면접 질문 10가지에서 확인할 수 있습니다.

FAQ

한 줄 답: 템플릿 인자의 요구를 이름으로 표현하려면 개념을 정의한 뒤 타입 제약이나 requires 절로 템플릿 선언에 연결하면 됩니다.

C++20에서 템플릿 인자를 이름으로 제약하려면 무엇을 사용합니까? concept 정의와 타입 제약 또는 requires 절을 사용합니다. 직접 요구를 적어야 하면 requires 식으로 유효성 검사를 정의합니다.

requires 절과 requires 식은 무엇이 다릅니까? requires 절은 템플릿이나 함수 선언에 연결하는 제약이고, requires 식은 요구 목록을 검사해 bool prvalue를 만드는 식입니다.

표준 개념 이름은 어디서 확인합니까? <concepts> 헤더와 concepts library 문서에서 확인합니다. std::integral, std::same_as, std::convertible_to처럼 문서에 정의된 이름을 사용합니다.

제약을 어기면 언제 진단됩니까? 템플릿 인스턴스화 초기의 컴파일 시점에 진단됩니다. 진단에는 만족되지 않은 개념이나 요구가 표시될 수 있습니다.

출처

한 줄 답: 본문의 정의와 예시는 2026-09-01 기준으로 확인한 cppreference의 constraints·concepts·requires 문서를 바탕으로 정리했습니다.

  • Constraints and concepts — 제약의 정의와 개념 선언, 적용 위치, 논리 결합, 단락 평가, 진단을 확인했습니다.
  • Concepts library<concepts> 헤더와 std 네임스페이스의 표준 개념 이름, 구문적·의미적 요구를 확인했습니다.
  • requires keywordrequires 키워드가 템플릿 제약과 중첩 요구에 사용되는 방식을 확인했습니다.
  • Requires expressionrequires 식의 구문과 단순·타입·복합·중첩 요구, 지역 매개변수 규칙을 확인했습니다.

C++20 concepts let a template state, by name, which properties its arguments must satisfy. The requirement is visible at the template interface instead of being hidden in the implementation.

This is a general explanation synthesized from cppreference's constraints and concepts documentation checked on 2026-09-01. Details can vary with the standard version and implementation.

What are C++ concepts?

Short answer: A concept is a named compile-time predicate that groups requirements for a template argument and becomes part of the template interface.

Constraints can be attached to class templates, function templates, and templated functions including generic lambdas. They participate in selecting a suitable overload or template specialization from the available candidates. Because a named requirement is evaluated at compile time, the declaration itself can show what kind of argument the template expects.

A concept is defined at namespace scope by pairing a name with a constraint expression. Its basic form is template <typename T> concept Name = constraint-expression;. When the concept name is used as a condition, it acts like a predicate whose result is true when the requirement is satisfied and false otherwise.

The following Hashable is a documentation example from cppreference's constraints page. It is not a concept supplied by the <concepts> standard library.

template<typename T>
concept Hashable = requires(T a)
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};

The same Hashable<T> constraint can be attached at several syntax positions. These are alternative ways to express one constraint, not four declarations that should all be combined on the same function:

  • Type constraint: template<Hashable T> void f(T) {}
  • A requires clause after the template parameter list: template<typename T> requires Hashable<T> void f(T) {}
  • A trailing requires clause on the function declarator: template<typename T> void f(T) requires Hashable<T> {}
  • An abbreviated function-template parameter: void f(Hashable auto) {}

In type-constraint notation, the type inferred in that context is implicitly passed as the concept's first argument. That is why the ordinary Hashable<T> condition can be shortened to Hashable T.

Concept definitions also have limits. A concept cannot refer to itself recursively, and it cannot constrain a template parameter that already has another constraint. Explicit instantiation, explicit specialization, and partial specialization of a concept are not permitted.

How do requires clauses and requires expressions differ?

Short answer: A requires clause attaches a condition to a template or function, while a requires expression performs the validity checks used to form that condition.

A requires clause can appear immediately after the template parameter list or at the end of a function declarator. The expression that follows must be a constant expression related to the template arguments. It is commonly a named concept, a logical combination of concepts, or a requires expression. A condition that looks like a function call may need parentheses so that it remains one expression.

In the example below, requires (T x) { x + x; } is the requires expression that defines Addable. In the function declaration, requires Addable<T> is the requires clause that attaches the resulting condition to add.

template<typename T>
concept Addable = requires (T x) {
    x + x;
};

template<typename T>
requires Addable<T>
T add(T a, T b) {
    return a + b;
}

The same clause can use the trailing form: template<typename T> T add(T a, T b) requires Addable<T> { return a + b; }. A constant expression such as requires true is syntactically valid, but a real constraint should normally communicate a meaningful concept or validity check.

A requires expression produces a bool prvalue. It can be written as requires { requirement-seq } or as requires (local-parameter-list) { requirement-seq }. C++20 defines four requirement categories:

  • Simple requirement: a + b; checks whether the expression is valid; it does not evaluate the expression.
  • Type requirement: typename T::inner; checks whether a nested type or type name can be used.
  • Compound requirement: { x + 1 } -> std::same_as<int>; checks expression validity together with a result-type constraint. It can also express convertibility, as in { *x } -> std::convertible_to<typename T::inner>;.
  • Nested requirement: after requires, it adds another condition based on local parameters.

Local parameters in a requires expression are notation for the validity check, not actual objects with storage, lifetime, or linkage. The parameter list cannot contain default arguments and cannot end with an ellipsis.

Where can you find standard concepts?

Short answer: The standard library's named requirements are documented in the concepts library, provided through the <concepts> header, and placed in the std namespace.

These concepts provide vocabulary for checking template arguments at compile time and selecting code paths according to type properties. The core language concepts are defined in <concepts> and live in the std namespace.

Five commonly used names are:

NameMeaning
std::integralThe type is an integral type.
std::same_as<T, U>Checks whether two types are the same.
std::convertible_to<From, To>Checks whether From is implicitly convertible to To.
std::derived_from<Derived, Base>Checks whether one type is derived from another.
std::floating_pointThe type is a floating-point type.

Narrower integer vocabulary includes std::signed_integral and std::unsigned_integral. The same standard-library documentation also covers comparison, object, and callable concepts, while the iterator, algorithms, and ranges libraries define additional concepts. Those areas are useful places to look up more vocabulary without changing the basic pattern.

Once the header and namespace are available, a type constraint can be written directly in the parameter list:

#include <concepts>

template<std::integral T>
T twice(T value) {
    return value + value;
}

std::integral T is a type constraint that passes the inferred T as the concept's first argument. The function template is therefore a candidate only when the type used for the call satisfies the integral requirement.

Standard concepts may combine syntactic and semantic requirements. A concept is satisfied when its syntactic requirements are met, and it is modeled when the semantic requirements are also honored. In general, compilers can directly check only the syntactic portion.

Why use constraints instead of SFINAE-style filtering?

Short answer: Named constraints expose the requirement in the template interface and diagnose an unmet condition at compile time early in template instantiation, which can make the source of an error easier to follow.

Earlier template filtering sometimes relied on whether substitution failed while an argument was being formed. The useful point here is the interface difference created by connecting a named requirement directly to the declaration, not an implementation tutorial for that filtering technique.

Templates whose conditions are not met are screened at the beginning of instantiation. This does not make errors disappear. It can, however, make diagnostics point to the unsatisfied named concept and its condition instead of leaving the reader with only a long candidate list and a generic operation error.

Because named requirements are part of the template interface, readers can understand the accepted argument range before inspecting the function body. When constraints are combined with &&, they are checked from left to right; if the left condition is not satisfied, substitution for the right side is not attempted. This short-circuiting can avoid an unnecessary substitution failure outside the immediate context, and || combinations short-circuit as well.

A constraint name should describe the semantic category handled by the code, such as a number or a callable object, rather than merely listing one operation that happens to be available. For a separate reference on the breadth of C++ language features, see 10 C++ technical interview questions; it is a language-feature reference, not concepts documentation.

FAQ

Short answer: Define a concept for the requirement, then connect it to the template declaration with a type constraint or a requires clause.

What should I use to name a template-argument requirement in C++20? Use a concept definition with a type constraint or a requires clause. When the direct validity checks need to be written out, use a requires expression.

How is a requires clause different from a requires expression? The clause attaches a constraint to a template or function declaration. The expression checks a requirement list and yields a bool prvalue.

Where do I look up standard concept names? Consult the <concepts> header and the concepts library documentation for names such as std::integral, std::same_as, and std::convertible_to.

When is a violated constraint diagnosed? It is diagnosed at compile time during the early stage of template instantiation. The diagnostic may identify the unsatisfied concept or requirement.

Sources

Short answer: The definitions and examples here are summarized from cppreference's constraints, concepts, and requires documentation checked as of 2026-09-01.

  • Constraints and concepts — constraints, concept declarations, attachment positions, logical combinations, short-circuiting, and diagnostics.
  • Concepts library — the <concepts> header, standard concept names in the std namespace, and syntactic versus semantic requirements.
  • requires keyword — how the requires keyword is used for template constraints and nested requirements.
  • Requires expression — requires-expression syntax, the simple, type, compound, and nested requirement categories, and local-parameter rules.

+ Recent posts