Constraints and concepts
이 페이지는 실험적인 핵심 언어 기능을 설명합니다. 표준 라이브러리 명세에 사용되는 명명된 타입 요구사항에 대해서는 named requirements 를 참조하십시오.
클래스 템플릿 , 함수 템플릿 , 그리고 비템플릿 함수들(일반적으로 클래스 템플릿의 멤버들)은 제약 조건 과 연관될 수 있으며, 이는 템플릿 인자에 대한 요구사항을 명시하여 가장 적합한 함수 오버로드와 템플릿 특수화를 선택하는 데 사용될 수 있습니다.
제약 조건은 지정된 요구 사항을 충족하는 유형으로만 변수 선언 및 함수 반환 유형에서 자동 유형 추론을 제한하는 데에도 사용될 수 있습니다.
이러한 요구 사항들의 명명된 집합을 개념(concepts) 이라고 합니다. 각 개념은 컴파일 시간에 평가되는 술어(predicate)이며, 제약 조건으로 사용되는 템플릿의 인터페이스 일부가 됩니다:
#include <string> #include <locale> using namespace std::literals; // 개념 "EqualityComparable"의 선언. 이는 타입 T에 대해 값 a와 b가 있을 때 // 표현식 a==b가 컴파일되고 그 결과가 bool로 변환 가능한 모든 타입 T를 만족함 template<typename T> concept bool EqualityComparable = requires(T a, T b) { { a == b } -> bool; }; void f(EqualityComparable&&); // 제약된 함수 템플릿의 선언 // template<typename T> // void f(T&&) requires EqualityComparable<T>; // 동일한 내용의 긴 형태 int main() { f("abc"s); // OK, std::string은 EqualityComparable을 만족함 f(std::use_facet<std::ctype<char>>(std::locale{})); // 오류: EqualityComparable을 만족하지 않음 }
제약 조건 위반은 컴파일 시간에, 템플릿 인스턴스화 과정 초기에 감지되어 이해하기 쉬운 오류 메시지를 제공합니다.
std::list<int> l = {3,-1,10}; std::sort(l.begin(), l.end()); //컨셉 없이 일반적인 컴파일러 진단 메시지: // invalid operands to binary expression ('std::_List_iterator<int>' and // 'std::_List_iterator<int>') // std::__lg(__last - __first) * 2); // ~~~~~~ ^ ~~~~~~~ // ... 50 lines of output ... // //컨셉을 사용한 일반적인 컴파일러 진단 메시지: // error: cannot call std::sort with std::_List_iterator<int> // note: concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied
컨셉의 의도는 구문적 제약(HasPlus, Array)보다는 의미론적 범주(Number, Range, RegularFunction)를 모델링하는 것입니다. ISO C++ 핵심 가이드라인 T.20 에 따르면, "의미 있는 의미론을 명시할 수 있는 능력은 구문적 제약과 대비되는 진정한 컨셉의 정의적 특성입니다."
기능 테스트가 지원되는 경우, 여기서 설명하는 기능들은 매크로 상수 __cpp_concepts 가 201507 이상의 값으로 표시됩니다.
목차 |
플레이스홀더
제약 없는 자리표시자
auto
와
제약된 자리표시자
인
concept-name
<
template-argument-list
(optional)
>
형태는 추론될 타입을 위한 자리표시자입니다.
플레이스홀더는 변수 선언(이 경우 초기화자로부터 추론됨) 또는 함수 반환 타입(이 경우 반환문으로부터 추론됨)에 나타날 수 있습니다.
std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // 첫 번째 auto는 int, // 두 번째 auto는 char Sortable x = f(y); // x의 타입은 f의 반환 타입에서 추론됨, // 해당 타입이 Sortable 제약 조건을 만족할 때만 컴파일됨 auto f(Container) -> Sortable; // 반환 타입은 return 문에서 추론됨 // 해당 타입이 Sortable을 만족할 때만 컴파일됨
플레이스홀더는 매개변수에도 나타날 수 있으며, 이 경우 플레이스홀더가 제약된 경우 함수 선언을 (제약된) 템플릿 선언으로 전환합니다
void f(std::pair<auto, EqualityComparable>); // 이것은 두 개의 매개변수를 가진 템플릿입니다: // 제약 없는 타입 매개변수와 제약이 있는 비타입 매개변수
제약된 자리 표시자는 auto 가 사용될 수 있는 모든 곳에서 사용될 수 있으며, 예를 들어 제네릭 람다 선언에서 사용됩니다
auto gl = [](Assignable& a, auto* b) { a = *b; };
제약된 형식 지정자가 비-형식 또는 템플릿을 지정하지만, 제약된 플레이스홀더로 사용되는 경우 프로그램은 형식이 잘못되었습니다:
template<size_t N> concept bool Even = (N%2 == 0); struct S1 { int n; }; int Even::* p2 = &S1::n; // 오류: 비타입 개념의 잘못된 사용 void f(std::array<auto, Even>); // 오류: 비타입 개념의 잘못된 사용 template<Even N> void f(std::array<auto, N>); // 정상
축약형 템플릿
함수 매개변수 목록에 하나 이상의 자리 표시자(placeholder)가 나타나면, 해당 함수 선언은 실제로 함수 템플릿 선언이며, 그 템플릿 매개변수 목록에는 고유한 각 자리 표시자마다 발명된 매개변수가 나타난 순서대로 포함됩니다
// 짧은 형태 void g1(const EqualityComparable*, Incrementable&); // 긴 형태: // template<EqualityComparable T, Incrementable U> void g1(const T*, U&); // 더 긴 형태: // template<typename T, typename U> // void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>; void f2(std::vector<auto*>...); // 긴 형태: template<typename... T> void f2(std::vector<T*>...); void f4(auto (auto::*)(auto)); // 긴 형태: template<typename T, typename U, typename V> void f4(T (U::*)(V));
동등 제약 조건을 가진 타입 지정자에 의해 도입된 모든 플레이스홀더는 동일한 발명된 템플릿 매개변수를 가집니다. 그러나 각 비제약 지정자(
auto
)는 항상 다른 템플릿 매개변수를 도입합니다
void f0(Comparable a, Comparable* b); // 긴 형태: template<Comparable T> void f0(T a, T* b); void f1(auto a, auto* b); // 긴 형태: template<typename T, typename U> f1(T a, U* b);
함수 템플릿과 클래스 템플릿 모두
template introduction
를 사용하여 선언할 수 있으며, 이는 다음과 같은 구문을 가집니다:
concept-name
{
parameter-list
(optional)
}
. 이 경우
template
키워드는 필요하지 않습니다: template introduction의
parameter-list
에 있는 각 매개변수는 해당 개념에서 대응하는 매개변수의 종류(타입, 비타입, 템플릿)에 따라 결정되는 종류의 템플릿 매개변수가 됩니다.
템플릿을 선언하는 것 외에도, 템플릿 소개는 소개로 명명된 개념을 지칭하는(변수 개념의 경우) 또는 호출하는(함수 개념의 경우) 술어 제약 조건 (아래 참조)을 연관시킵니다.
EqualityComparable{T} class Foo; // 긴 형태: template<EqualityComparable T> class Foo; // 더 긴 형태: template<typename T> requires EqualityComparable<T> class Foo; template<typename T, int N, typename... Xs> concept bool Example = ...; Example{A, B, ...C} struct S1; // 긴 형태 template<class A, int B, class... C> requires Example<A,B,C...> struct S1;
함수 템플릿의 경우, 템플릿 소개는 플레이스홀더와 결합될 수 있습니다:
Sortable{T} void f(T, auto); // 긴 형태: template<Sortable T, typename U> void f(T, U); // 플레이스홀더만 사용한 대안: void f(Sortable, auto);
|
이 섹션은 불완전합니다
이유: 템플릿 선언 페이지를 이곳으로 연결하도록 수정 필요 |
개념
개념(concept)은 명명된 요구 사항들의 집합입니다. 개념의 정의는 네임스페이스 범위에 나타나며, 함수 템플릿 정의의 형태(이 경우 함수 개념(function concept) 이라고 함) 또는 변수 템플릿 정의의 형태(이 경우 변수 개념(variable concept) 이라고 함)를 가집니다. 유일한 차이점은 concept 키워드가 decl-specifier-seq 에 나타난다는 점입니다:
// 표준 라이브러리의 변수 개념 (Ranges TS) template <class T, class U> concept bool Derived = std::is_base_of<U, T>::value; // 표준 라이브러리의 함수 개념 (Ranges TS) template <class T> concept bool EqualityComparable() { return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; }; }
함수 개념에는 다음과 같은 제한 사항이 적용됩니다:
-
inline과constexpr은 허용되지 않으며, 함수는 자동으로inline과constexpr이 됩니다 -
friend와virtual은 허용되지 않습니다 -
예외 명세는 허용되지 않으며, 함수는 자동으로
noexcept(true)가 됩니다 - 나중에 선언하고 정의할 수 없으며, 재선언할 수 없습니다
-
반환 타입은 반드시
bool이어야 합니다 - 반환 타입 추론은 허용되지 않습니다
- 매개변수 목록은 반드시 비어 있어야 합니다
-
함수 본문은 오직
return문으로만 구성되어야 하며, 그 인수는 반드시 제약식 이어야 합니다(술어 제약, 다른 제약들의 논리곱/�리합, 또는 아래 참조의 requires-expression)
다음 제한 사항이 변수 개념에 적용됩니다:
-
반드시
bool타입을 가져야 합니다 - 초기화 없이 선언할 수 없습니다
- 클래스 범위에서 선언하거나 정의할 수 없습니다
-
constexpr을 지정할 수 없으며, 변수는 자동으로constexpr이 됩니다 - 초기화식은 제약 조건 표현식이어야 합니다 (술어 제약, 제약의 논리곱/�리합, 또는 아래 참조의 requires-표현식)
개념은 함수 본문이나 변수 초기화 부분에서 자기 자신을 재귀적으로 참조할 수 없습니다:
template<typename T> concept bool F() { return F<typename T::type>(); } // 오류 template<typename T> concept bool V = V<T*>; // 오류
개념의 명시적 인스턴스화, 명시적 특수화, 또는 부분 특수화는 허용되지 않습니다 (제약 조건의 원래 정의 의미를 변경할 수 없음)
제약 조건
제약 조건은 템플릿 인자에 대한 요구사항을 명시하는 논리 연산의 시퀀스입니다. 이들은 requires-expression 내부에서(아래 참조) 그리고 개념의 본문으로 직접 나타날 수 있습니다.
제약 조건에는 9가지 유형이 있습니다:
처음 세 가지 유형의 제약 조건은 개념의 본문으로 직접 나타나거나 임시 requires-절로 나타날 수 있습니다:
template<typename T> requires // requires-clause (임시 제약 조건) sizeof(T) > 1 && get_value<T>() // 두 개의 predicate 제약 조건의 논리곱 void f(T);
동일한 선언에 여러 제약 조건이 부착될 때, 전체 제약 조건은 다음 순서로 결합됩니다: 템플릿 도입 에 의해 도입된 제약 조건, 나타나는 순서대로 각 템플릿 매개변수에 대한 제약 조건, 템플릿 매개변수 목록 뒤의 requires 절, 나타나는 순서대로 각 함수 매개변수에 대한 제약 조건, 후행 requires 절:
// 두 선언은 동일한 제약 조건을 가진 함수 템플릿을 선언함 // 제약 조건: Incrementable<T> && Decrementable<T> template<Incrementable T> void f(T) requires Decrementable<T>; template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // ok // 다음 두 선언은 서로 다른 제약 조건을 가짐: // 첫 번째 선언은 Incrementable<T> && Decrementable<T> // 두 번째 선언은 Decrementable<T> && Incrementable<T> // 논리적으로는 동등하지만 // 두 번째 선언은 잘못된 형식이며, 진단이 요구되지 않음 template<Incrementable T> requires Decrementable<T> void g(); template<Decrementable T> requires Incrementable<T> void g(); // error
접속사
제약 조건의 논리곱
P
와
Q
는
P
&&
Q
로 지정됩니다.
// 표준 라이브러리의 개념 예시 (Ranges TS) template <class T> concept bool Integral = std::is_integral<T>::value; template <class T> concept bool SignedIntegral = Integral<T> && std::is_signed<T>::value; template <class T> concept bool UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
두 제약 조건의 논리곱(conjunction)은 두 제약 조건 모두가 만족될 때만 만족됩니다. 논리곱은 왼쪽에서 오른쪽으로 평가되며 단락 평가(short-circuited)됩니다 (왼쪽 제약 조건이 만족되지 않으면 오른쪽 제약 조건으로의 템플릿 인자 치환은 시도되지 않습니다: 이는 immediate context 외부의 치환으로 인한 실패를 방지합니다). 사용자 정의
operator&&
오버로드는 제약 조건 논리곱에서 허용되지 않습니다.
논리합
제약 조건의 논리합
P
와
Q
는
P
||
Q
로 지정됩니다.
두 개의 제약 조건의 논리합(disjunction)은 두 제약 조건 중 하나라도 만족되면 충족됩니다. 논리합은 왼쪽에서 오른쪽으로 평가되며 단락 평가(short-circuited)됩니다 (왼쪽 제약 조건이 만족되면 오른쪽 제약 조건으로의 템플릿 인자 추론은 시도되지 않습니다). 사용자 정의
operator||
오버로드는 제약 조건 논리합에서 허용되지 않습니다.
// 표준 라이브러리의 예제 제약 조건 (Ranges TS) template <class T = void> requires EqualityComparable<T>() || Same<T, void> struct equal_to;
술어 제약 조건
술어 제약 조건은 bool 타입의 상수 표현식입니다. 이는 true 로 평가될 때만 만족됩니다.
template<typename T> concept bool Size32 = sizeof(T) == 4;
술어 제약 조건은 비타입 템플릿 매개변수와 템플릿 템플릿 인자에 대한 요구 사항을 지정할 수 있습니다.
술어 제약 조건은 직접 bool 로 평가되어야 하며, 변환은 허용되지 않습니다:
template<typename T> struct S { constexpr explicit operator bool() const { return true; } }; template<typename T> requires S<T>{} // 잘못된 predicate 제약: S<T>{}는 bool이 아님 void f(T); f(0); // 오류: 제약 조건이 절대 만족되지 않음
요구사항
키워드 requires 는 두 가지 방식으로 사용됩니다:
template<typename T> void f(T&&) requires Eq<T>; // 함수 선언자의 마지막 요소로 나타날 수 있음 template<typename T> requires Addable<T> // 또는 템플릿 매개변수 목록 바로 뒤에 나타날 수 있음 T add(T a, T b) { return a + b; }
true
이고, 그렇지 않으면 false입니다:
template<typename T> concept bool Addable = requires (T x) { x + x; }; // requires-expression template<typename T> requires Addable<T> // requires-clause, not requires-expression T add(T a, T b) { return a + b; } template<typename T> requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice T add(T a, T b) { return a + b; }
requires-expression 의 문법은 다음과 같습니다:
requires
(
매개변수-목록
(선택사항)
)
{
요구사항-시퀀스
}
|
|||||||||
| parameter-list | - |
함수 선언에서와 같은 쉼표로 구분된 매개변수 목록이지만, 기본 인자는 허용되지 않으며 마지막 매개변수는 생략 부호일 수 없습니다. 이러한 매개변수는 저장 공간, 링크age 또는 수명이 없습니다. 이러한 매개변수는
requirement-seq
의 닫는
}
까지 유효 범위에 있습니다. 매개변수가 사용되지 않는 경우, 둥근 괄호도 생략할 수 있습니다.
|
| requirement-seq | - | 공백으로 구분된 요구 사항 시퀀스 (각 요구 사항은 세미콜론으로 끝남). 각 요구 사항은 이 requires-expression이 정의하는 제약 조건의 논리곱 에 또 다른 제약 조건을 추가합니다. |
requirements-seq 내의 각 요구사항은 다음 중 하나입니다:
- 단순 요구사항
- 타입 요구사항
- 복합 요구사항
- 중첩 요구사항
요구 사항은 현재 범위 내에 있는 템플릿 매개변수와 parameter-list 에서 도입된 지역 매개변수를 참조할 수 있습니다. 매개변수화된 경우, requires-표현식은 parametrized constraint 를 도입한다고 말합니다.
템플릿 인자를 requires-expression에 대입하는 것은 해당 요구사항에서 유효하지 않은 타입이나 표현식의 형성을 초래할 수 있습니다. 이러한 경우,
- requires-expression이 템플릿화된 엔티티 선언 외부에서 사용될 때 치환 실패가 발생하면 프로그램의 형식이 잘못되었습니다.
- requires-expression이 템플릿화된 엔티티 선언에서 사용되는 경우, 해당 제약 조건은 "만족되지 않음"으로 처리되며 치환 실패는 오류가 아닙니다 , 그러나
- 가능한 모든 템플릿 인자에 대해 requires-expression에서 치환 실패가 발생할 경우, 프로그램의 형식이 잘못되었으며 진단이 필요하지 않습니다:
template<class T> concept bool C = requires { new int[-(int)sizeof(T)]; // 모든 T에 대해 유효하지 않음: 형식 오류, 진단 불필요 };
단순 요구사항
단순 요구 사항은 임의의 표현식 문입니다. 이 요구 사항은 표현식이 유효해야 한다는 것입니다(이는 표현식 제약 조건 입니다). 술어 제약 조건과 달리 평가는 수행되지 않으며, 오직 언어적 정확성만 검사됩니다.
template<typename T> concept bool Addable = requires (T a, T b) { a + b; // "a+b 표현식이 컴파일 가능한 유효한 표현식임" }; // 표준 라이브러리의 예제 제약 조건 (ranges TS) template <class T, class U = T> concept bool Swappable = requires(T&& t, U&& u) { swap(std::forward<T>(t), std::forward<U>(u)); swap(std::forward<U>(u), std::forward<T>(t)); };
타입 요구사항
타입 요구사항은 키워드 typename 뒤에 선택적으로 한정된 타입 이름이 오는 형태입니다. 이 요구사항은 명명된 타입이 존재해야 한다는 것( 타입 제약 )입니다: 이를 통해 특정 명명된 중첩 타입이 존재하는지, 클래스 템플릿 특수화가 타입을 명명하는지, 또는 별칭 템플릿이 타입을 명명하는지 검증하는 데 사용할 수 있습니다.
template<typename T> using Ref = T&; template<typename T> concept bool C = requires { typename T::inner; // 요구되는 중첩 멤버 이름 typename S<T>; // 요구되는 클래스 템플릿 특수화 typename Ref<T>; // 요구되는 별칭 템플릿 치환 }; // 표준 라이브러리의 개념 예시 (Ranges TS) template <class T, class U> using CommonType = std::common_type_t<T, U>; template <class T, class U> concept bool Common = requires (T t, U u) { typename CommonType<T, U>; // CommonType<T, U>가 유효하고 타입을 명명함 { CommonType<T, U>{std::forward<T>(t)} }; { CommonType<T, U>{std::forward<U>(u)} }; };
복합 요구사항
복합 요구사항(compound requirement)은 다음과 같은 형태를 가집니다.
{
expression
}
noexcept
(선택적)
trailing-return-type
(선택적)
;
|
|||||||||
그리고 다음 제약 조건들의 논리곱을 지정합니다:
noexcept
가 사용되면, 표현식 또한 noexcept이어야 합니다 (
예외 제약 조건
)
template<typename T> concept bool C2 = requires(T x) { {*x} -> typename T::inner; // *x 표현식이 유효해야 함 // AND T::inner 타입이 유효해야 함 // AND *x의 결과가 T::inner로 변환 가능해야 함 }; // 표준 라이브러리 개념 예시 (Ranges TS) template <class T, class U> concept bool Same = std::is_same<T,U>::value; template <class B> concept bool Boolean = requires(B b1, B b2) { { bool(b1) }; // 직접 초기화 제약은 표현식을 사용해야 함 { !b1 } -> bool; // 복합 제약 requires Same<decltype(b1 && b2), bool>; // 중첩 제약, 아래 참조 requires Same<decltype(b1 || b2), bool>; };
중첩 요구사항
중첩 요구 사항은 세미콜론으로 끝나는 또 다른 requires-clause 입니다. 이것은 로컬 매개변수에 적용된 다른 명명된 개념들을 사용하여 표현된 predicate constraints (위 참조)를 도입하는 데 사용됩니다 (requires 절 외부에서는 predicate constraints가 매개변수를 사용할 수 없으며, 표현식을 requires 절에 직접 배치하면 expression constraint가 되어 평가되지 않습니다)
// Ranges TS의 예제 제약 조건 template <class T> concept bool Semiregular = DefaultConstructible<T> && CopyConstructible<T> && Destructible<T> && CopyAssignable<T> && requires(T a, size_t n) { requires Same<T*, decltype(&a)>; // 중첩: "Same<...>가 true로 평가됨" { a.~T() } noexcept; // 복합: "a.~T()"는 예외를 던지지 않는 유효한 표현식 requires Same<T*, decltype(new T)>; // 중첩: "Same<...>가 true로 평가됨" requires Same<T*, decltype(new T[n])>; // 중첩 { delete new T }; // 복합 { delete new T[n] }; // 복합 };
개념 해석
다른 함수 템플릿과 마찬가지로, 함수 개념(변수 개념은 제외)은 오버로드될 수 있습니다: 동일한 concept-name 을 사용하는 여러 개념 정의를 제공할 수 있습니다.
개념 해석은 concept-name (한정될 수 있음)이 다음 위치에 나타날 때 수행됩니다
template<typename T> concept bool C() { return true; } // #1 template<typename T, typename U> concept bool C() { return true; } // #2 void f(C); // C로 참조되는 개념 집합에는 #1과 #2가 모두 포함됨; // 개념 해석(아래 참조)은 #1을 선택함.
개념 해석을 수행하기 위해, template parameters 는 이름(및 한정자, 있는 경우)과 일치하는 각 개념의 템플릿 매개변수들이 concept arguments 시퀀스와 대응됩니다. 개념 인수는 템플릿 인수들과 wildcards 로 구성됩니다. 와일드카드는 모든 종류의 템플릿 매개변수(타입, 비타입, 템플릿)와 일치할 수 있습니다. 인수 집합은 컨텍스트에 따라 다르게 구성됩니다.
template<typename T> concept bool C1() { return true; } // #1 template<typename T, typename U> concept bool C1() { return true; } // #2 void f1(const C1*); // <wildcard> matches <T>, selects #1
template<typename T> concept bool C1() { return true; } // #1 template<typename T, typename U> concept bool C1() { return true; } // #2 void f2(C1<char>); // <wildcard, char> matches <T, U>, selects #2
template<typename... Ts> concept bool C3 = true; C3{T} void q2(); // OK: <T> matches <...Ts> C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
template<typename T> concept bool C() { return true; } // #1 template<typename T, typename U> concept bool C() { return true; } // #2 template <typename T> void f(T) requires C<T>(); // matches #1
개념 해석은 각 인자를 각각 보이는 개념의 해당 매개변수와 매칭하여 수행됩니다. 기본 템플릿 인자(사용된 경우)는 인자에 대응하지 않는 각 매개변수에 대해 인스턴스화된 후 인자 목록에 추가됩니다. 템플릿 매개변수는 동일한 종류(타입, 비타입, 템플릿)를 가질 경우에만 인자와 매칭되며, 인자가 와일드카드인 경우는 예외입니다. 매개변수 팩은 모든 인자가 패턴과 종류상 매칭되는 한(와일드카드인 경우 제외) 0개 이상의 인자와 매칭됩니다.
어떤 인수가 해당 매개변수와 일치하지 않거나, 매개변수보다 인수가 더 많고 마지막 매개변수가 팩이 아닌 경우, 해당 컨셉은 실현 가능하지 않습니다. 실현 가능한 컨셉이 없거나 둘 이상인 경우 프로그램은 형식에 맞지 않습니다.
template<typename T> concept bool C2() { return true; } template<int T> concept bool C2() { return true; } template<C2<0> T> struct S1; // 오류: <와일드카드, 0>이 // <typename T>와 <int T> 둘 다 일치하지 않음 template<C2 T> struct S2; // #1과 #2 모두 일치: 오류
|
이 섹션은 불완전합니다
이유: 의미 있는 개념을 가진 예제가 필요하며, 이러한 'return true' 자리 표시자는 적절하지 않습니다 |
제약 조건의 부분 순서
추가 분석을 진행하기 전에, 모든 이름 개념과 모든 requires 표현식의 본체를 치환하여 남은 것이 원자적 제약 조건(술어 제약 조건, 표현식 제약 조건, 타입 제약 조건, 암시적 변환 제약 조건, 인자 추론 제약 조건, 예외 제약 조건)에 대한 연접과 선접의 연속으로 구성되도록 제약 조건들이 정규화 됩니다.
개념
P
가 개념
Q
를
포섭한다
고 말하는 것은, 타입과 표현식의 동등성을 분석하지 않고
P
가
함의한다는
것을 증명할 수 있을 때입니다 (따라서
N >= 0
은
N > 0
을 포섭하지 않습니다)
구체적으로, 먼저
P
는 논리합 정규형으로 변환되고
Q
는 논리곱 정규형으로 변환된 후 다음과 같이 비교됩니다:
-
각 원자적 제약 조건
A은 동등한 원자적 제약 조건A를 포함합니다 -
각 원자적 제약 조건
A은 논리합A||B를 포함하지만 논리곱A&&B는 포함하지 않습니다 -
각 논리곱
A&&B은A를 포함하지만 논리합A||B은A를 포함하지 않습니다
서브섬션 관계는 제약 조건들의 부분 순서를 정의하며, 이를 통해 다음을 결정하는 데 사용됩니다:
- 비템플릿 함수에 대한 가장 적합한 후보를 오버로드 해결 에서 선택
- 오버로드 집합에서 비템플릿 함수의 주소 취하기
- 템플릿 템플릿 인자에 대한 최적의 매치
- 클래스 템플릿 특수화의 부분 순서 지정
- 부분 순서 지정 함수 템플릿의
|
이 섹션은 불완전합니다
이유: 위에서 이곳으로의 역링크 |
선언
D1
과
D2
가 제약 조건을 가지며 D1의 정규화된 제약 조건이 D2의 정규화된 제약 조건을 포함하는 경우(또는 D1이 제약 조건을 가지고 D2가 제약 조건을 가지지 않는 경우), D1은 D2보다
적어도 동등하게 제약된
상태라고 합니다. D1이 D2보다 적어도 동등하게 제약되고 D2가 D1보다 적어도 동등하게 제약되지 않는 경우, D1은 D2보다
더 제약된
상태입니다.
template<typename T> concept bool Decrementable = requires(T t) { --t; }; template<typename T> concept bool RevIterator = Decrementable<T> && requires(T t) { *t; }; // RevIterator는 Decrementable을 포함하지만 그 반대는 성립하지 않음 // RevIterator는 Decrementable보다 더 제약적임 void f(Decrementable); // #1 void f(RevIterator); // #2 f(0); // int는 Decrementable만 만족하므로 #1 선택 f((int*)0); // int*는 두 제약 조건 모두 만족하므로 더 제약적인 #2 선택 void g(auto); // #3 (비제약) void g(Decrementable); // #4 g(true); // bool은 Decrementable을 만족하지 않으므로 #3 선택 g(0); // int는 Decrementable을 만족하므로 더 제약적인 #4 선택
키워드
컴파일러 지원
GCC >= 6.1은 이 기술 사양을 지원합니다 (필요 옵션 - fconcepts )