C++20에서 추가된 Requires와 Concept은 타입이 가져야 하는 요구 조건을 정의하는 문법입니다.

사용자가 만들 수 도 있고, 미리 만들어 져 있는 C++ 표준 Concept로도 제공합니다.

Requires와 Concept 개요

  • 템플릿 인자가 가져야 하는 조건을 표기하는 문법

  • 조건을 만족하는 경우에만 템플릿 사용

  • Concept 뿐 아니라 type traits 등도 사용 가능

  • 신규 키워드 : concept, requires

  • 신규 헤더 : concepts

Requires Clauses

장점 및 특징

  • 템플릿에 requires + (조건)으로 제약조건 표기 가능

  • 컴파일 시간에 결정되는 bool 형식의 상수 값(반드시 bool 타입이어야 함)

  • 조건을 함수 형식(constexpr or consteval 함수)으로 사용가능( 괄호로 묶기 반드시 필요 )

  • 읽기 좋은 에러 메세지 출력 ( 통상적으로 템플릿 사용시 에러 메세지가 매우 복잡 )

  • 치환 실패는 에러가 아니라 SFINAE 규칙 적용

    #include <type_traits>
    
    template<typename T> requires std::is_integral_v<T> // 문법 "requires + (조건)" 로 제약 조건 명시 가능
    void goo(T arg)
    {
    }
    
    template<typename T> requires std::is_integral_v<T>
    T gcd(T a, T b)
    {
        return b == 0 ? a : gcd(b, a % b);
    }
    
    double gcd(double a, double b) { return 0; };
    
    int main
    {
        goo(1); // OK
        goo(3.4); // Error : 에러 메세지가 가독성 좋게 출력됨
    
        gcd(4, 2); // OK : template을 사용해서 int 버전 함수 생성
        gcd(4.2, 2.1); // OK : double 버전
        gcd(4.2f, 2.1f) // OK : float 버전에서 % 연산으로 인해 에러가 발생함으로 컴파일러가
                        // requires 조건을 만족하지 않을 경우 템플릿을 사용하지 않은 double 버전 함수 선택
                        // SFINAE라는 기술로 구현 ( Subsituation failure is not an error )
    }
    
  • Requires 절과 함수 오버로딩

    #include <iostream>
    #include <vector>
    #include <list>
    #include <concepts>
    
    template<typename T> requires std::random_access_iterator<T> // 반복자 임의 접근 가능한지 여부( C++ 표준 concepts )
    void MyAdvance(T& p, int n) // 간단한 구현을 위해 n > 0 인 경우만
    {
        // 임의 접근
        p = p + n; 
    }
    
    template<typename T> requires std::input_iterator<T>
    void MyAdvance(T& p, int n)
    {
        // 임의 접근 아닌 경우
        while(n--) ++p;
    }
    
    int main()
    {
        // C++20 에서는 concepts을 이용하여 advance를 아주 손쉽게 구현 할 수 있다.
        std::vector v = {1, 2, 3, 4, 5, 6};
        std::list l = {1, 2, 3, 4, 5, 6};
    
        MyAdvance(std::begin(v), 4);
        MyAdvance(std::begin(l), 4);
    }
    
  • Requires 절의 위치

    template<typename T> requires true // 이 위치 OK
    void f1(T a)
    {}
    
    template<typename T> 
    void f2(T a) requires true // 이 위치도 OK
    {}
    

Concept

  • concept이란 requirements의 명명된 집합이며 다음과 같은 형태를 취합니다.

    template <파라메터 list>
    concept 컨셉이름 = 제약 조건 표현식;
    
  • 예제

    #include <type_traits>
    
    template<typename T>
    concept Integral = std::is_integral_v<T> && true; // 요구 조건 제약사항 concepts 생성
    
    template<typename T> requires Integral<T>
    void foo(T a)
    {
    
    }
    
    int main()
    {
        bool b = Integral<int>;
    
        std::cout << b << std::endl;
    }
    
  • Requires expression

  • 형태 : requires ( 파라메터 list[생략가능] ) { 요구사항; };

    #include <type_traits>
    
    template<typename T> concept True = true; // 기본형
    
    template<typename T> concept Modulus = requres(T a, T b) // Requires expression 형태 : 조건을 디테일 하게 명시 할 수 있음
    {
        a % b;
    };
    
    template<typename T> requires Modules<T> T Mod(T a, T b) // requires 절 : 일반적 템플릿에 requires와 위에서 만든 concept을 사용
    {
        return a % b;
    }
    
    int main()
    {
        Mod(10, 5); // Ok
        Mod(5.4, 2.2); // Error : 일치하는 오버로드 함수가 없음, concept의 제약조건을 % 연산에 대해서만 명시 했기 때문에 ( 실수는 % 연산 불가 )
    }
    
  
  - 이러한 원리를 이용해서 표준에서 concepts 헤더에 미리 구현해놓은 다양한 concept이 존재합니다.
  
- **Partial ordering : 여러개의 concept을 만족  경우 어떤 concept을 적용 하는지에 대한 규칙입니다.**

  ```c++
  template<typename T>
  concept C1 = sizeof(T) > 1;
  
  template<typename T>
  concept C2 = C1<T> && sizeof(T) < 8; // 기존의 C1을 활용하여 조건 확장 가능
  
  template<typename T> requires C1<T>
  void foo(T a) { }
  
  template<typename T> requires C2<T>
  void foo(T a) { }
  
  int main()
  {
      foo(3); // int : C1과 C2를 만족하는데, 어떤 concept을 사용할지? -> 보다 많은 조건을 만족하는 concept 실행
  }
  • concepts의 syntax sugar

    #incldue <concepts>
    
    template<typename T> requires std::integral<T> // 기본 표기법
    void f1(T a){ }
    
    template<std::integral T> // 이 표현도 같은 의미, 더욱 간결
    void f2(T a){ }
    
    void f3()(std::integral auto a){ } // 이 표현도 같은 의미이며 템플릿입니다. 더 더욱 간결
    
    // 추가정보 : C++20의 간결해진 템플릿 표현법( 표준이지만 아직 VC에서 지원 하지 않음, 추후에 지원 될 예정 )
    void easy_template()(auto a){ } // c++20 부터 템플릿 작성시 이러한 표현으로 가능하며 의미는 == template<typeame T> void 함수명(T a)
    
  • C++20 표준이 제공하는 concepts은 다음에서 참조 -> 링크

더 많은 C++20 관련 정보