Concepts: Generic Programming의 미래
모든 내용은 Bjarne Stroustrup 교수님의 Good_Concepts에서 발췌하였습니다.
3. Concepts (개념) 사용하기
Concepts는 컴파일 시간 술어 (조건) 입니다. ( 즉, boolean 값을 생성하는 것 입니다. )
예를들어 템플릿 타입 인수 T가 있을 때, 다음과 같이 요구될 수 있습니다.
- 반복자 타입 : Iterator<T>
- 임의 접근 반복자 타입 : Random_access_iterator<T>
- 숫자 타입 : Number<T>
표기법 C<T>에서 C는 Concepts이고 ( 위의 리스트가 이에 해당 ), 이는 'T가 충족되면 참' 을 의미하는 표현식 입니다.
C의 모든 조건이 맞지 않다면 거짓입니다.
마찬가지로 template 인수 집합이 술어를 충족해야 한다는 것도 지정할 수 있습니다.
예를 들어 Mergeable<In1, In2, Out> 과 같은 다중 형식 술어는 STL과 대부분의 다른 응용 프로그램 도메인을 설명하기 위해 필요합니다.
이것들은 매우 표현력이 뛰어나고, 컴파일하기에 저렴합니다. ( TMP 해결 방법보다 저렴합니다. )
학생들은 한 두 번의 강의 후에 Concepts를 사용할 수 있다. 물론 직접 자신만의 Concepts를 정의할 수 있고,
Concepts 라이브러리를 가질 수 있습니다. ( 5번 항목 참조 )
Concepts는 overloading을 가능하게 하고, 많은 ad-hoc 메타 프로그래밍과 많은 메타프로그래밍 scaffolding code의 필요성을 제거하며 generic programming도 현저하게 단순화 시킵니다.
3.1 템플릿 인터페이스 지정
먼저 Concepts를 사용하여 알고리즘을 지정하는 방법을 살펴보겠습니다.
std::find는 한 쌍의 Iterator대신 Sequence를 사용합니다.
template <typename S, typename T>
requires Sequence<S> && Equality_comparable<Value_type<S>, T>
Iterator_of<S> find(S& seq, const T& value);
라인별로 살펴봅시다.
- 이것은 두 개의 템플릿 유형 인수를 사용하는 템플릿입니다.
- 첫 번째 템플릿 인수는 Sequence(Sequence<S>)여야 하며, operator==를 통한 시퀀스 요소의 비교가 가능해야합니다. (Equality_comparable<Value_type<S>, T>)
- find() 함수는 reference로 Sequence를 가져오고, const reference로 찾을 값을 받습니다. 그리고, 반복자를 반환합니다.
Sequence는 begin()과 end()가 있지만, find()를 이해하기 위한 것입니다. 중요하진 않습니다.
Value_type<S> 와 Iterator_of<S>를 사용하기 위해 별칭 템플릿 ( alias templates ) 을 사용했습니다.
정의는 다음과 같습니다.
template <typename X> using Value_type<X> = X::value_type;
template <typename X> using Iteartor_of<X> = X::Iterator;
별칭 템플릿 ( alias templates )은 Concepts와 특별한 관련이 없습니다. 표현하는데 유용합니다.
Equality_comparable Concepts는 표준 라이브러리 Concepts로 제안됩니다.
en.cppreference.com/w/cpp/concepts/equality_comparable
그것은 operator==와 operator!=를 요구합니다. ( 이는 두 가지 인수를 취한다는 점을 유의하세요. )
많은 Concepts들이 2개 이상의 인수를 취합니다.
Concepts는 유형(types) 뿐만 아니라 유형간의 관계(relationships)도 표현할 수 있습니다.
이것은 STL과 기타 대부분의 라이브러리에 필수적입니다. ( 이러한 관계는 다음에 설명됩니다. )
Concepts는 단지 '유형의 유형' 만이 아닙니다.
이제 find의 사용에 대해 보겠습니다.
void use(vector<string>& vs, list<double>& lstd)
{
auto p0 = find(vs, "Waldo"); // Well
auto p1 = find(vs, 0.5772); //Ill-formed, string과 double의 비교를 할 수 없습니다.
auto p2 = find(lstd, 0.5772); // Well
auto p3 = find(lstd, "Waldo"); // Ill-formed, double과 string의 비교를 할 수 없습니다.
if(p0 != vs.end()) { /* found Waldo */ }
// ...
}
이것은 단지 하나의 예이고 아주 간단한 것입니다.
Ranges TS[Nie15] 에서 Concepts 사용에 대한 더 많은 예를 찾을 수 있습니다. ( Ranges에 대한 정리도 해둘 것입니다. )
3.2 속기 표기
템플릿 인수가 Sequence가 되도록 요구할 때, 다음과 같이 작성할 수 있습니다.
template <typename Seq> requires Sequence<Seq>
void algo(Seq& s);
즉 Sequence여야하는 Seq 유형의 인수가 필요합니다.
하지만 다음과 같이 작성했을때,
template <Sequence Seq>
void algo(Seq& s);
이건 위의 더 긴 버전과 정확히 동일하지만, 더 짧고 원했던 함수와 일치합니다.
마찬가지로 우리는 '동물이 있고, 닭이 있다' 라고 말하지 않습니다. '닭이 있다.' 라고 말합니다.
이처럼 Concepts에 대해서는 재작성 규칙이 간단하고 일반적입니다.
template <C T>
는 곧
template <typename T> requires C<T>
와 동일합니다.
이 간단한 속기를 하나의 Concepts에 사용합니다.
즉, 우리가 원할때 단순화 할 수 있습니다.
예를 들어
template <typename S, typename T>
requires Sequence<S> && Equality_comparable<Value_type<S>, T>
Iterator_of<S> find(S& seq, const T& value);
에서
template <Sequence S, typaname T>
requires Equality_comparable<Value_type<S>, T>
Iterator_of<S> find(S& seq, const T& value);
이 짧은 형태가 긴 버전에 비해 명확성을 크게 향상 시킨다고 생각한다.
주로 다중 형식 Concepts에 대해서 ( 예 : Equality_comparable<Value_type<S>, T> )
그리고, 템플릿 인수 type을 반복적으로 참조해야 하는 경우 ( 예 : find의 S ) 명시적 requires를 사용합니다.
4. Concepts 정의
종종 라이브러리에서 Equality_comparable과 같은 유용한 Concepts를 찾을 수 있습니다 ( 예 : Ranges )
표준 라이브러리의 Concepts들을 보길 바랍니다. 어떻게 Concepts들을 정의했는지 확인해보세요.
template <typename T>
concept quality_comparable =
requires(T a, T b) {
{ a == b } -> std::same_as<bool>;
{ a != b } -> std::same_as<bool>;
};
Equality_comparable Concepts는 변수 템플릿 ( variable template )으로 정의됩니다.
T 형은 반드시 bool 형을 반환하는 operator==와 operator!=를 제공해야 합니다. ( 기술적으로, bool로 변할 수 있다면 가능 )
요구 표현 ( requires expression ) 은 형식 ( type ) 을 어떻게 사용할 수 있는지를 직접 표현할 수 있게 해줍니다.
- { a == b }는 operator==를 사용하여 두 T를 비교할 수 있어야 한다고 말합니다.
- { a == b } -> bool 은 이러한 비교 결과가 bool ( 기술적으로, bool로 변할 수 있다면 가능 ) 이여야 한다고 말합니다.
요구 표현은 실제로 실행 ( execute )되지 않습니다. 대신, 컴파일러가 요구 사항들을 하나씩 살펴 봅니다.
그리고, 컴파일이 가능하다면 true를, 불가능 하다면 false를 반환합니다.
하나의 예제를 더 봅시다.
template <typename T>
concept bool Sequence =
requires(T t) {
typename Value_type<T>; // 반드시 value type을 가지고 있어야 합니다.
typename Iterator_of<T>; // 반드시 iterator type을 가지고 있어야 합니다.
{ begin(t) } -> Iterator_of<T>; // 반드시 begin() 과 end() 가 있어야 합니다.
{ end(t) } -> Iterator_of<T>;
requires Input_Iteartor<iterator_of<T>>;
requires Same_type<Value_type<T>, Value_type<Iterator_of<T>>>;
};
Sequence는 다음과 같습니다.
- 타입 T는 두 개의 관련된 값인 ( two associated type ) Value_type<T>와 Iterator_of<T>를 가져야 합니다.
Value_type<T>와 Iterator_of<T>는 그저 평범한 별칭 템플릿 ( alias template )일 뿐입니다.
이러한 유형을 요구 표현에 나열하면, T 유형이 Sequence가 되어야 함을 나타 냅니다. - 타입 T는 각각 적절한 iterator를 반환하는 begin()과 end()를 제공해야 합니다.
- 적절한 (appropriate) iterator 를 통해 T의 iterator 타입이 input_iterator여야 함을 의미합니다.
Value_type은 해당 Iterator의 value type과 동일해야 합니다.
Input_Iterator와 Same_type은 라이브러리의 개념이지만, 누구나 쉽게 작성할 수 있습니다.
마지막으로, Sortable을 살펴보겠습니다.
정렬하려면 타입이 Sequence 타입이여야 하며, 임의 접근 (random access)가 가능하고, operator<를 지원해야 합니다.
template <typename T>
concept Sortable =
Sequence<T> &&
Random_access_iterator<iterator_of<T>> &&
Less_than_comparable<Value_type<T>>;
Random_access_iterator 와 Less_than_comparable 은 Equal_compatible과 유사하게 정의되어 있으며 라이브러리에서 조회할 수 있습니다.
종종 concepts 간의 관계( relationship )에 대한 요구사항을 만들고 싶어합니다.
예를 들어, Equality_compatible Concepts를 단일 유형을 요구하도록 정의했지만, 일반적으론 다음과 같은 두 가지 유형을 처리하도록 정의 됩니다.
template <typename T, typename U>
concept Equality_comparable =
requires (T a, U b) {
{ a == b } -> std::same_as<bool>;
{ a != b } -> std::same_as<bool>;
{ b == a } -> std::same_as<bool>;
{ b != a } -> std::same_as<bool>;
};
이것은 int를 double로, string을 char* 로 비교하는것을 허용하지만, int를 string과 비교할 순 없습니다.
'C++ > Modern' 카테고리의 다른 글
C++20) Concepts ( 콘셉트, 개념 ) - 4 (0) | 2020.11.29 |
---|---|
C++20) Concepts ( 콘셉트, 개념 ) - 3 (0) | 2020.11.28 |
C++20) Concepts ( 콘셉트, 개념 ) - 1 (0) | 2020.11.25 |
C++20) Coroutine ( 코루틴 ) - 3 (0) | 2020.08.23 |
C++20) Coroutine ( 코루틴 ) - 2 (0) | 2020.08.21 |