C++20에서 추가된 Ranges는 C#의 linq와 흡사하게 컨테이너의 데이터를 손쉽게 다룰 수 있는 다양한 알고리즘을 제공합니다.

Ranges 소개

  • 신규 헤더 ranges 추가

  • 아이템들의 집합

  • iterable 할 수 있어야 함, concept range = requires -> 최소 요구조건 begin(), end()

    • 배열, std 컨테이너들 등

Views

  1. DB의 view와 비슷하게 원본 컨테이너의 포인터 컨테이너를 만들어 원하는 만큼의 가시성을 제공함
  2. view 끼리 합 연산이 가능함
  3. view 변환이 가능함
  4. 지연된 연산을 수행함 ( 명시 시점이 아닌, 실제 사용시 연산이 수행됨 )
#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    std::ranges::take_view tv(v, 3); // auto r1 v | std::views::take(3); 와 동일하며 뷰를 생성함
    auto r1 v | std::views::take(3); // 같은 의미 다른 표현
    
   std::cout << typeid(r1).name() << std::endl;
   std::cout << typeid(tv).name() << std::endl;
    
    std::ranges::transform_view trv(tv, [](int a) { std::cout << "op" << std::endl; return a*2; });
    
    // 위의 take_view와 transform을 합친 표현법
    auto r = v | std::views::take(3) | std:views::transform( [](int a){ return a*2; });
    
    std::cout << "start iterating" << std::endl;
    
    auto p1 = std::ranges::begin(trv); 
    std::cout << *p1 << std::endl; // 이 타이밍에 지연된 연산이 수행됨 -> 출력 : 2
    
    for(auto n : r)
        std::cout << n << std::endl; // 지연된 연산 : 2, 4, 6 출력
}

Ref_view

  1. ref_view는 기존의 range에 대한 reference
  2. 내부적으로 포인터 연산을 수행하는 구조
#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    std::ranges::ref_view rv(v);
    
    auto p1 = rv.begin();
    auto p2 = v.begin();
    
    std::cout << *p1 << std::endl; // 1
    std::cout << *p2 << std::endl; // 2
    
    std::cout << &v << std::endl;
    std::cout << &(rv.base(()) << std::endl; // 위 주소와 동일, rv가 참조하던 원본 주소 출력
}
  1. vector에 대한 기존 &(참조) 문법을 사용하면 되는데, ref_view를 사용하는 이유?
#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> v1 = {1, 2, 3, 4, 5}
    std::vector<int> v2 = {6, 7, 8, 9, 10}
    
    // 기존 C++의 참조 : 이동 불가능한 참조
    std::vector<int>& r1(v1);
    std::vector<int>& r2(v2);
    
    r1 = r2; // 이 의미는? -> 이렇게 대입 시 vector 자체를 복사
    
    // ref_view를 사용시 std::refernce_wraaper의 range 버전이며, 이동 가능한 참조
    std::ranges::ref_view rf1(v1);
    std::ranges::ref_view rf2(v2);
    
    std::cout << v1[0] << std:;endl; // 1
    std::cout << v2[0] << std::endl; // 6
    
    std::cout << rf1[0] << std:;endl; // 6
    std::cout << rf2[0] << std::endl; // 6
}
  1. 활용 예
template<typename R> class take_view
{
    R rg;
    int count;
public:
    template<typename A>
    take_view(A&& r, int cnt) : rg(std::forward<A>(r)), count(cnt)
        
    // TODO : begin(), end() 구현
};
template<typename A>
take_view(A&&, int) -> take_view< std::ranges::ref_view<std::remove_reference_t<A> > >;

int main()
{
    std::vector<int> v1 = { 1, 2, 3, 4, 5};
    std::vector<int> v1 = { 6, 7, 8, 9, 10};
    
    take_view tv1(v1, 3);
    take_view tv2(v2, 3);
    
    tv1 = tv2;
}

Reverse_view

view를 거꾸로 볼 수 있음

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> v1 = { 1, 2, 3, 4, 5};
    
    std::ranges::revers_view rv1(v);
    // auto rv1 = std::views::revers(v); // 위와 동일 표현
    
    for(auto n : rv1 )
        std:;cout << n << ", ";
}

Filter_view

원하는 조건 필터링 가능

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5};
    
    std::ranges::filter_view fv(v, [](int n) { return n % 2 == 0 }); // 짝수만 필터링
    // auto fv = std::views::filter(fv2, 3); // 같은 의미, 함수 객체를 생성하는 방법
    // auto fv = v | std::views::filter( [](int n) { retgurn n % 2 = 0; }); // 파이프라인을 사용하는 같은 표현법 | 을 이용해서 여러
    																		// 조건을 합칠 수 있음
    
    for(auto n : v1)
        std::cout << n << std::endl;
}

Range 응용

  • 정렬과 치환
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5};
    std::sort(v.begin(), v.end()); // 기존 C++ 정렬 알고리즘 -> 인자로 반복자 전달
    
    std::range::sort(v); // Range의 정렬 알고리즘 -> 인자로 Range를 전달, 코드가 간결해짐
    				     // sort에 lamda로 정렬 조건 전달 가능
    
    auto fv = v | std::views::filter([](int n) { return n % 2 == 0; });
    
    std::ranges::replace_if( fv, [](int n) { return n > 0;}, 0); //0보다 큰 것을 0으로 치환
    
    for ( auto n : v)
        std::cout << n << std::endl;
}
  • projection
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <fuctional>
#include <string>

struct Person
{
    std::string name;
    int age;
}

int main
{
    std::vector<Person> v;
    
    v.emplace_back("aa", 20);
    v.emplace_back("xx", 22);
    v.emplace_back("cc", 11);
    v.emplace_back("oo", 54);
    v.emplace_back("xx", 43);
    
    // range를 이용해서 나이순으로 간결하게 정렬하기
    //std::ranges::sort(v, const Person& p1, const Person& p2) { return p1.age < p2.age; }); // C++17 이전 가능하던 방법
    
    // C++20 range 활용 방법
    std::ranges::sort(v, std::less{}, &Person::age); // 구조체의 age를 기준으로 정렬( 새로운 방법 -> projection )
    
	for (auto&& p : v)
        std::cout << p.name << " : " << p.age << std::endl;
}

더 많은 C++20 관련 정보