C++20 리서치 - range의 view, ref_view, reverse_view, filter_view[7]
C++20에서 추가된 Ranges는 C#의 linq와 흡사하게 컨테이너의 데이터를 손쉽게 다룰 수 있는 다양한 알고리즘을 제공합니다.
Ranges 소개
-
신규 헤더 ranges 추가
-
아이템들의 집합
-
iterable 할 수 있어야 함, concept range = requires -> 최소 요구조건 begin(), end()
- 배열, std 컨테이너들 등
Views
- DB의 view와 비슷하게 원본 컨테이너의 포인터 컨테이너를 만들어 원하는 만큼의 가시성을 제공함
- view 끼리 합 연산이 가능함
- view 변환이 가능함
- 지연된 연산을 수행함 ( 명시 시점이 아닌, 실제 사용시 연산이 수행됨 )
#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
- ref_view는 기존의 range에 대한 reference
- 내부적으로 포인터 연산을 수행하는 구조
#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가 참조하던 원본 주소 출력
}
- 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
}
- 활용 예
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;
}