C++20 리서치 - Requires 절, Concept[6]
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은 다음에서 참조 -> 링크