← SEARCHABLE ARCHIVE
C / C++ · 2026-08-10

C++ 기초부터 마스터까지 — 16화

Concepts와 requires로 템플릿의 요구사항 표현하기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

템플릿은 여러 타입에 같은 규칙을 적용하지만, 사용할 수 없는 타입이 들어오면 오류가 구현 깊숙한 곳에서 길게 나타나기 쉽다. C++20의 Concepts와 requires는 템플릿이 기대하는 연산을 선언부에서 표현한다.

제약 없는 템플릿의 문제

template <typename T>
int GetHealth(const T& value)
{
    return value.Health();
}

이 함수는 Health()를 호출할 수 있는 타입만 사용 가능하지만 시그니처에는 그 조건이 보이지 않는다. 잘못된 타입을 전달하면 함수 본문을 인스턴스화한 뒤에야 오류가 발생한다.

Concept으로 요구사항 이름 붙이기

#include <concepts>

template <typename T>
concept Damageable = requires(T value, int amount)
{
    { value.Health() } -> std::convertible_to<int>;
    { value.ApplyDamage(amount) } -> std::same_as<void>;
};

template <Damageable T>
void Attack(T& target, int damage)
{
    target.ApplyDamage(damage);
}

requires 표현식은 코드를 실행하지 않고 해당 문법과 결과 타입이 유효한지 검사한다. 이제 Attack이 무엇을 필요로 하는지 호출 전에 읽을 수 있고, 조건을 만족하지 않는 타입은 더 가까운 위치에서 컴파일 오류가 난다.

값 조건도 제약할 수 있다

template <typename T>
concept SmallId = std::integral<T> && (sizeof(T) <= 4);

template <SmallId T>
void WriteId(T id);

Concept은 타입 특성과 requires 표현식을 논리 연산으로 조합할 수 있다. 오버로드가 여러 개라면 컴파일러는 만족하는 후보 중 더 구체적인 제약을 가진 함수를 선택할 수 있다.

void Print(std::integral auto value);
void Print(std::floating_point auto value);

auto 매개변수에 Concept을 붙이는 축약 함수 템플릿도 가능하다. 정수와 부동소수점의 의미가 다를 때 하나의 함수 안에서 if constexpr로 모든 경우를 섞는 것보다 분리된 계약이 읽기 쉽다.

문법 검사가 의미를 보장하지는 않는다

a < b가 컴파일된다는 사실만으로 비교가 일관된 순서를 만든다는 보장은 없다. 복사 가능하다는 문법과 실제로 독립된 복사본을 만든다는 의미도 다르다.

표준 Concept은 문법뿐 아니라 지켜야 할 의미적 요구사항을 문서화한다. 사용자 정의 Concept도 이름과 주석으로 의미를 설명하고, 중요한 규칙은 테스트해야 한다.

Concept을 모든 템플릿 한 줄마다 붙이면 재사용 범위를 불필요하게 줄일 수 있다. 함수 구현과 공개 계약에 실제로 필요한 최소 요구사항을 표현하는 것이 핵심이다.