프로그래밍/C,C++

[Modern C++20] concept

GONII 2025. 4. 8. 12:29

 

🧠 concept란?

C++ 템플릿에 제약 조건(조건식)을 줄 수 있는 문법

📌 “이 타입은 이런 행동을 할 수 있어야 템플릿에 넣을 수 있다”
✅ 안 되면 컴파일 에러를 깔끔하게 내줌 (더 이상 무시무시한 템플릿 에러 아님!)


🧩 예시 없이 설명 못 하지!

#include <concepts>
#include <iostream>

template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
};

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

int main() {
    std::cout << add(3, 4) << '\n';        // OK
    // std::cout << add("hi", "there") << '\n'; // 컴파일 에러!
}

🔍 위 예제 해석:

  • Addable은 concept 이름
  • requires(...) 안에 조건이 있음
    • T 타입은 + 연산이 가능해야 하고
    • 결과도 T로 변환 가능해야 함
  • add 함수는 오직 Addable 타입만 받을 수 있음

💥 concept을 안 쓰면 이런 불편함이 있어…

template<typename T>
T add(T a, T b) {
    return a + b;
}
  • 타입 T가 +를 지원하지 않으면?
  • 👉 컴파일 에러 메시지가 10줄~100줄 나오는 템플릿 지옥!

✅ concept을 쓰면?

  • 컴파일 에러 메시지가 짧고 명확해짐
  • 코드 가독성 높아짐
  • 템플릿 제약 조건을 구조적으로 표현 가능

🔧 다양한 concept 종류

C++ 표준 라이브러리에 이미 많이 정의돼 있어:

concept 이름  의미
std::integral 정수형 타입
std::floating_point 실수형 타입
std::same_as<T> 정확히 타입이 T일 때
std::derived_from<T> T를 상속받은 타입
std::default_initializable 기본 생성 가능
std::convertible_to<A, B> A가 B로 변환 가능

🧪 사용 예시

1. 함수 파라미터 제한

template <std::integral T>
T multiply_by_two(T x) {
    return x * 2;
}

2. requires 키워드 사용

template<typename T>
requires std::floating_point<T>
T square_root(T x) {
    return sqrt(x);
}

3. 함수 선언 후 requires 추가

template<typename T>
T square(T x) requires std::integral<T> {
    return x * x;
}

3.1 requires 표현식 고급버전

template<typename T>
concept HasPushBack = requires(T a, typename T::value_type v) {
    { a.push_back(v) };
};

// T는 push_back 함수가 있고
// 그 인자로는 value_type을 넣을 수 있어야 함
// 예: std::vector, std::list는 OK
// std::map은 안 됨 (value_type 구조 다름)

🔀 4. 여러 concept 조합

template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;

template<Number T>
T half(T x) {
    return x / 2;
}
// int, float OK
// std::string ❌ 에러

📦 5. 클래스 템플릿에 concept 적용

template<std::integral T>
class MyCounter {
public:
    void increment() {
        ++value;
    }

private:
    T value = 0;
};
// MyCounter<int> OK
// MyCounter<std::string> ❌ 컴파일 에러

💎 6. concept + requires + trailing return type

template<typename T>
requires requires(T a) {
    { a.size() } -> std::convertible_to<std::size_t>;
}
auto get_size(const T& t) -> std::size_t {
    return t.size();
}
// T는 size() 멤버 함수를 가져야 하고,
// 그 결과가 std::size_t로 변환 가능해야만 OK

🧪 복합 예제

#include <concepts>
#include <vector>
#include <list>

template<typename T>
concept SequenceContainer = requires(T a, typename T::value_type v) {
    { a.begin() } -> std::same_as<typename T::iterator>;
    { a.end() } -> std::same_as<typename T::iterator>;
    { a.push_back(v) };
    typename T::value_type;
    typename T::iterator;
};

template<SequenceContainer T>
void fill_with_42(T& container) {
    for (int i = 0; i < 5; ++i) {
        container.push_back(42);
    }
}

void main()
{
    std::vector<int> v;
    fill_with_42(v); // OK

    std::list<int> l;
    fill_with_42(l); // OK

    std::map<int, int> m;
    // fill_with_42(m); // ❌ 컴파일 에러
}

🚀 언제 쓰면 좋을까?

  • 템플릿 인자가 어떤 **기능(연산자, 멤버 등)**을 꼭 지원해야 할 때
  • 추상적 타입 설계 시 타입 안정성 높이기
  • SFINAE (enable_if)보다 훨씬 읽기 쉽고 유지보수에 유리

🤯 비교: enable_if vs concept

항목 enable_if (C++11~17) concept (C++20)
문법 복잡하고 읽기 어려움 명확하고 선언적
에러 메시지 길고 추적 어려움 간결하고 의미 있음
사용성 제한적 강력하고 유연함

📌 요약

키포인트 설명
개념 템플릿 타입의 조건을 선언적으로 표현
키워드 concept, requires
효과 타입 제약 + 에러 개선 + 가독성 향상
대체 가능 기술 enable_if, SFINAE

📚 reference

https://en.cppreference.com/w/cpp/concepts

 

Concepts library (since C++20) - cppreference.com

The concepts library provides definitions of fundamental library concepts that can be used to perform compile-time validation of template arguments and perform function dispatch based on properties of types. These concepts provide a foundation for equation

en.cppreference.com

 

반응형