C++

C++20 : std::ranges #1 - views

Sorting 2021. 3. 17. 01:42
반응형

std::ranges 가 드디어 c++20 에서 표준화 되었으나,

github.com/ericniebler/range-v3 에서 구현됐던 내용의 일부만 들어오거나

다른 부분들이 있어, 이번 기회에 VS2019에 구현된 내용들만 정리해보고자 한다.

 

현재 사용중인 VS버전은 다음과 같다.

Visual Studio Community 2019 - 16.8.5

라이브러리는 16.6버전에서 최초로 릴리즈 되었으므로

혹시 버전이 위 버전보다 낮다면, 업데이트를 해야 한다.

 

VS에서 <ranges> 라이브러리 등의 c++20 피쳐를 사용하려면,

프로젝트 속성에서 다음 옵션을 줘야 한다.

 

 

 

VS2019에 구현되어있는 views


  • views::single
  • views::all
  • views::filter
  • views::transform
  • views::take
  • views::reverse
  • views::drop

 

안타깝게도 views::iota 는 아직 구현되어있지 않다.
테스트용 range를 가장 쉽게 만들 수 있는 방법인데 조금 아쉽다.

 

컨테이너를 range 로 변환


가장 기본적인 range를 만들어보자.
다음과 같이 1부터 10까지의 숫자를 가진 vector 가 있다.

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

 

이 vector 를 통째로 range로 만들려면
views::all 를 사용하면 된다.

  std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  auto r = std::ranges::views::all(v);

 

 

Stream Operator 오버로딩


ranges-v3 라이브러리에서는 모든 view 가 상속받는 view_interface 에

operator << 가 오버로딩 되어있어서,

std::cout << r; 만 하면 출력이 가능했다.

 

하지만 표준에는 (개인적으로는 매우) 안타깝게도 해당 내용이 들어오지 않았다.

 

아래 코드를 사용하면 해당 기능을 임시로 사용할 수 있다.

template<class Rng>
requires std::is_base_of_v<std::ranges::view_interface<Rng>, Rng>
std::ostream& operator<<(std::ostream& os, Rng& rng)
{
	auto it = std::ranges::begin(rng);
	const auto e = std::ranges::end(rng);
	if (it == e)
		return os << "[]";
	os << '[' << *it;
	while (++it != e)
		os << "," << *it;
	os << ']';
	return os;
}

 

이제 아래와 같이 range 를 바로 출력할 수 있다.

std::cout << r << std::endl; // [1,2,3,4,5,6,7,8,9,10]

 

이제 준비도 끝났으니, view 를 한번씩 확인해보자.

 

 

views::single


views::single 은 한 개의 값을 range 로 만들 때 사용한다.

auto r = std::ranges::views::single(1);
std::cout << r << std::endl; // [1]

 

값이란, range 도 포함한다.

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto r1 = std::ranges::views::all(v); 
auto r2 = std::ranges::views::single(r1);
std::cout << r2 << std::endl; // [[1,2,3,4,5,6,7,8,9,10]]

 

views::filter


view 란, range 를 여러 모양으로 보는 방법을 제공한다.

실제 데이터가 담겨있는 컨테이너(위 코드에서는 std::vector<int> v)는 수정하지 않고,

컨테이너의 내용을 range 로 순회할 수 있다.

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto rng = std::ranges::views::all(v) 
	| std::ranges::views::filter([](int a) { 
    		return a % 2 == 0; }); // 짝수만 필터링
	
std::cout << rng << std::endl; // [2,4,6,8,10]

// 원본은 수정되지 않음.
auto orinRange = std::ranges::views::all(v);
std::cout << orinRange << std::endl; // [1,2,3,4,5,6,7,8,9,10]

ranges 를 처음 보는 분들은 갑자기 bit OR 연산자가 왜나오는지 의아하실 수 있다.

range view 연산은 | 연산자를 통해 결합(파이프라이닝)될 수 있다.

 

기존의 C++ 함수 호출 방식은 다음과 같이, 호출의 순서와는 반대로 작성된다.

filter(all(v));

 

하지만, 파이프라인을 사용하면, 호출의 순서와 동일하게 코드를 작성할 수 있다.

all(v) | filter()

 

 

views::transform


transform() 은 range의 내용을 변경하며 순회할 때 사용한다.

 

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto rng = std::ranges::views::all(v)
	| std::ranges::views::filter([](int a) { 
    		return a % 2 == 0; })
	| std::ranges::views::transform([](int a) {
    		return a * a; });

std::cout << rng << std::endl; // [4,16,36,64,100]

 

개인적으로는 id 로 연관된 객체를 찾아 별도의 컨테이너를 만들 때 유용하게 사용될 것 같다.

 

 

views::take


take() 는 range 중, 맨 앞에 n개만 골라서 별도의 range를 만들 때 사용한다.

 

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto rng = std::ranges::views::all(v)
	| std::ranges::views::filter([](int a) { 
    		return a % 2 == 0; })
	| std::ranges::views::transform([](int a) {
    		return a * a; })
	| std::ranges::views::take(3);
	
std::cout << rng << std::endl; // [4,16,36]

 

 

views::reverse


range 를 거꾸로 순회할 때 사용한다.

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto rng = std::ranges::views::all(v)
	| std::ranges::views::filter([](int a) { 
    		return a % 2 == 0; })
	| std::ranges::views::transform([](int a) {
    		return a * a; })
	| std::ranges::views::reverse
	| std::ranges::views::take(3);

std::cout << rng << std::endl; // [100,64,36]

 

 

 

views::drop


range 내의 처음 n 개의 원소를 스킵할 때 사용한다.

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto rng = std::ranges::views::all(v)
	| std::ranges::views::filter([](int a) { 
    		return a % 2 == 0; })
	| std::ranges::views::transform([](int a) {
    		return a * a; })
	| std::ranges::views::reverse
	| std::ranges::views::drop(1)
	| std::ranges::views::take(3);

std::cout << rng << std::endl; // [64,36,16]

 

 

예제를 짜면서, 조합성을 최대한 살리면서 작성해보았다.

 

아직 구현되지 않은 view들이 더 있는 것 같은데,

하루빨리 구현되면 좋겠다.

 

현업에서 사용하려면 아직 몇년 더 있어야겠지..

반응형