C++ 20 : Range(중요)

RuuNee
|2024. 5. 15. 17:00
반응형

Ranges란?

Ranges는 객체이다다. 어떠한 객체의 집합들입니다.

begin()을 이용해 처음의 객체를 얻어낼 수 있고, end()를 이용해 마지막 인자 다음의 끝을 알아낼 수 있다.

즉 Ranges는 begin과 end가 필수로 필요하며, 이를 통해 순회할 수 있어야 한다.

즉, 순회할 수 있는 아이템 그룹이라고 보면 된다. (ex : STL Container)

 

Views란?

view는 '요소를 소유하지 않은 범위'라고 한다.

조금 더 공식적으로, view는 다음과 같다.

  • 기본 생성 가능 ( is default constructible )
  • constant-time의 이동 연산과, 파괴 연산 ( destruction operations )이 있다.
  • (만약 복사 가능한 경우) constant-time의 복사 연산이 있다.
  • Range에 대해 적용할 수 있는 연산

사용하는 이유

  • 우선 코드가 간결해진다. 
    기존에 begin과 end를 사용하며 작성하던 코드가 대폭 간결해짐.
    '파이프 구문( | )'를 이용하여 계산식을 나타내며, 계산들을 조합하여 나타낼 수 있다.
    왼쪽에서 오른쪽으로 읽어나가기 때문에 보기또한 간편하다.

  • Lazy Operation , 불필요한 계산 회피, 무한 범위 표현 가능

  • 지연된 연산 가능 ( lazy operation ) : 불 필요한 계산은 회피한다. 필요할 때만 계산.
  • C#의 LINQ문법과 비슷한 느낌

 

 

vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };

 

예를 들어 우리가 벡터에서 짝수만을 추출해야 하는 상황이 있다고 해보자.

그러면 대부분 이런식으로 코드를 작성했을 것이다.

 

for (auto n : v1)
    if (n % 2 == 0)
        v2.push_back(n);

 

이 기능을 Range를 사용해서 다시 작성하면 아래와 같다.

 

auto results = v1 | std::views::filter([](int n) {return n % 2 == 0; }));

 

코드가 아주 간결해진걸 볼 수 있다.

주요 기능들 정리

std::views::all
std::ranges::filter_view / std::views::filter (조건 만족하는거 추출)
std::ranges::transform_view / std::views::transform (각 요소를 변환)
std::ranges::take_view / std::views::take (n개 요소를 추출)

std::ranges::take_while_view / std::views::take_while (조건 만족할 때까지 요소 추출)
std::ranges::drop_view / std::views::drop (n개 요소를 스킵)
std::ranges::drop_while_view / std::views::drop_while (조건 만족할 때까지 요소 스킵)
std::ranges::join_view / std::views::join (view 를 병합)
std::ranges::split_view / std::views::split (split)
std::ranges::reverse_view / std::views::reverse (역순서로 순회)
std::ranges::elements_view / std::views::elements (튜플의 n번째 요소를 대상으로 view 생성)
std::ranges::keys_view / std::views::keys (pair-like value의 첫번째 요소를 대상으로 view 생성)
std::ranges::values_view / std::views::values (pair-like value의 두번째 요소를 대상으로 view 생성)

 


그리고 sort를 해줄 때 도 std::sort(v1.begin(), v2.end()); 이런식으로 해주는게 아니라

 

std::ranges::sort(v1);

 

 

이런식으로 해줄 수도 있다.

 

	struct Knight
	{
		std::string name;
		int id;
	};

	vector<Knight> knights =
	{
		{"Test1",1},
		{"Test2",1},
		{"Test3",1},
		{"Test4",1},
		{"Test5",1}
	};

	std::ranges::sort(knights, {}, & Knight::name); //ascending by name

	std::ranges::sort(knights, std::ranges::greater(), & Knight::name); //descending by name

	std::ranges::sort(knights, {}, & Knight::id); //ascending by id 

	std::ranges::sort(knights, std::ranges::greater(), & Knight::id); //ascending by id

 


0~100 사이의 숫자중 소수인 5개의 숫자를 추출해야 한다면 이렇게 해줄 수도 있을 것이다.

 

	auto isPrime = [](int num)
	{
		if (num <= 1)
			return false;

		for (int n = 2; n * n <= num; n++)
			if (num % n == 0)
				return false;

		return true;
	};

	std::vector<int> v3;
	// std::views::iota(a, b) : a부터 시작해서 1씩 증가 b개를 만들어줌
	for (int n : std::views::iota(0, 100) | std::views::filter(isPrime) | std::views::take(5))
	{
		v3.push_back(n);
	}

 


마지막으로 Custom view를 만들어서 사용할 수도 있다.

 

template<std::ranges::input_range Range>
requires std::ranges::view<Range>
class ContainerView : public std::ranges::view_interface<ContainerView<Range>>
{
public:
	ContainerView() = default;

	constexpr ContainerView(Range r) : _range(std::move(r)), _begin(std::begin(r)), _end(std::end(r))
	{

	}

	constexpr auto begin() const { return _begin; }
	constexpr auto end() const { return _end; }

private:
	Range _range;
	std::ranges::iterator_t<Range> _begin;
	std::ranges::iterator_t<Range> _end;
};

template<typename Range>
ContainerView(Range&& range)->ContainerView<std::ranges::views::all_t<Range>>;

 

	std::vector<int> myVec{ 1,2,3,4,5 };
	auto myView = ContainerView(myVec);

	for (auto n : myView)
	{
		cout << n << endl;
	}

 

반응형

'C++' 카테고리의 다른 글

C++ 20 : Three-Way Comparison (< = > 연산자)  (0) 2024.05.16
C++ 20 : Corutine(중요)  (0) 2024.05.15
C++ 20 : Module(중요)  (0) 2024.05.14
C++ 20 : Concept(중요)  (0) 2024.05.14
C++ 20  (0) 2024.05.14