2024.02.22 - [C++/STL] - [C++ 20] Concepts 2
[C++ 20] Concepts 2
2023.05.23 - [C++/STL] - [C++20] Concepts [C++20] Concepts 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 co
donot-simsim.tistory.com
에서 이어지는 글.
Concept 에 대해 간단하게 알아봤으니, 이제 requires를 통해 concept 을 잘 써먹을 차례이다.
Requires
== 타입이 가져야 하는 제약 조건을 가진 표현식
compile time에 조건을 조사하여, 최종적으로 bool type의 prvalue를 생성한다
기본 형태는 아래와 같고,
requires {requirement-seq;}
requires (parameter-list) {requirement-seq;}
간단한 예제를 두 개 들어보자면, 아래와 같다.
template <typename T>
concept default_constructable = requires{
T{};
};
template <typename T>
concept dereferencable = requires (T a) {
*a;
};
그럼 결국 저 requirement-seq 에 무엇이 들어가느냐가 중요한데, 크게 총 네 가지로 나눌 수 있다.
Simple requirements
- 이 식이 사용 가능한가?
template <typename T>
concept dereferencable = requires (T a) {
*a;
};
template <typename T>
concept begin_function_is_exist = requires (T a) {
a.begin();
};
단순하게 생각하면 된다.
바로 밑에 적혀진 표현식이 사용 가능한가?
해당 값의 return이 어떻든 여기선 상관하지 않는다. 그저 사용 가능하기만 하면 된다.
template <typename T>
concept begin_function_is_exist = requires (T a) {
a.begin();
};
int main() {
std::cout << begin_function_is_exist<int> << std::endl; // false
std::cout << begin_function_is_exist<std::vector<int>> << std::endl; // true
}
추가로, 주의할 점이 하나 있다.
template <typename T>
concept C1 = sizeof(T) > 4;
template <typename T>
concept C2 = requires{
sizeof(T) > 4;
};
int main() {
std::cout << C1<short> << std::endl; // false
std::cout << C2<short> << std::endl; // true
}
이렇게, 비슷해 보이는 concept이지만 결과값은 다르다.
왜 그런지는 위에서 설명한
- 이 식이 사용 가능한가?
를 잘 생각해 보면 될 것이다.
Type requirements
- 이러한 type을 가지고 있는가?
template<typename T>
using Ref = T&;
template<typename T>
concept C = requires
{
typename T::inner; // required nested member name
typename S<T>; // required class template specialization
typename Ref<T>; // required alias template substitution
};
위와 같은 concept이 있다고 하면, 이 concept은
- T 안에 inner라는 type 이 있어야 하고 (T{ using inner = ???})
- 참조값을 가질 수 있으며
- S라는 어떠한 template에 들어갈 수 있어야 한다.
위 조건을 만족해야 한다.
template<class T, class U>
using CommonType = std::common_type_t<T, U>;
template<class T, class U>
concept Common = requires (T && t, U && u)
{
typename CommonType<T, U>; // CommonType<T, U> is valid and names a type
{ CommonType<T, U>{std::forward<T>(t)} };
{ CommonType<T, U>{std::forward<U>(u)} };
};
이 조건은
- 공통되는 common type이 있고,
- 해당 common type에 t를 인자로 넣어 생성 가능하며
- 해당 common type에 u를 인자로 넣어 생성 가능하다.
를 만족해야 한다.
requirements 에서 type 관련하여 확인이 가능!
Compound requirements
- expression의 결과가 return type과 같은가?
{ expression } noexcept(optional) return-type-requirement (optional) ;
expression의 결과가, return의 type-requirement를 만족하는지를 본다.
template <typename T>
concept pointer = std::is_pointer_v<T>;
template <typename T>
concept conceptTest1 = requires(T a, T b) {
{a + b} -> pointer;
{a.f1()} -> std::same_as<int>;
{b.f2()} -> std::convertible_to<bool>;
};
위와 같은 concept에서는
- a+b 의 결과값이 pointer인지
- a의 f1이라는 함수를 실행시킨 return값이 int 인지.
- b의 f2라는 함수를 실행시킨 return값이 bool로 변환이 가능한지.
를 확인한다.
다만 여기서 주의할 점이 있는데.
template <typename T>
concept pointer = std::is_pointer_v<T>;
template <typename T>
concept conceptTest1 = requires(T a, T b) {
{a + b} -> int; // error
{a + b} -> std::is_pointer_v; // error
{a + b} -> pointer; // ok.
};
이런 식으로, return 부분에 나오는 것은 Concept 로 지정이 될 수 있어야 한다.
nested requirements
- requires 안에 또 requires 가 들어갈 수 있다.
template<class T>
concept C1 = std::is_pointer_v<T> &&
requires(T a, std::size_t n)
{
requires std::is_same_v<T*, decltype(&a)>; // nested
requires std::is_same_v<T*, decltype(new T)>; // nested
};
추가로, requires 의 특성이 하나 더 있다.
조건을 만족하는 게 여러 개 있다면, 가장 많이 만족하는 쪽을 택하게 된다.
#include <iostream>
#include <concepts>
template <typename T> concept C1 = std::convertible_to<T, bool>;
template <typename T> concept C2 = sizeof(T) > 2;
template <typename T> concept C3 = std::is_integral_v<T>;
template <typename T> concept C4 = C1<T> && C2<T>;
template <typename T> concept C5 = C1<T> && C2<T> && C3<T>;
template <typename T> requires C1<T>
void f1(T a) { std::cout << "C1"; }
template <typename T> requires C4<T>
void f1(T a) { std::cout << "C2"; }
template <typename T> requires C5<T>
void f1(T a) { std::cout << "C3"; }
int main() {
f1(10); // C3
f1(3.14); // C2
f1('A'); // C1
return 0;
}
* 잘못된 정보는 덧글로 알려주시면 감사한 마음으로 수정하겠습니다.
'C++ > 기타' 카테고리의 다른 글
[C++] CRTP 패턴. (1) | 2024.02.13 |
---|---|
[C++] Statement vs Expression (0) | 2023.06.25 |
[C++] 함수에 const 한정자 (0) | 2023.06.15 |
[C++] type cast (static_cast, dynamic_cast, const_cast, reinterpret_cast) (0) | 2023.05.18 |
[C++17] if constexpr (1) | 2023.05.17 |