C++/기타

[C++ 20] requires

sseram 2024. 2. 22. 17:14
반응형

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;
}

 

 

 

 

 

 

 

 

* 잘못된 정보는 덧글로 알려주시면 감사한 마음으로 수정하겠습니다.

반응형