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들이 더 있는 것 같은데,
하루빨리 구현되면 좋겠다.
현업에서 사용하려면 아직 몇년 더 있어야겠지..
'C++' 카테고리의 다른 글
C++20 : std::ranges #3 - range 를 컨테이너로 변환하기 (0) | 2021.03.24 |
---|---|
C++20 : std::ranges #2 - iota_view 만들기 (0) | 2021.03.22 |
C++ : shrink_to_fit() (1) | 2020.12.13 |
L1 캐시는 왜 Data와 Inst로 나뉘어 있나요? (번역) (0) | 2020.12.13 |
TBB - 병렬 컨테이너 사용 (concurrent_hash_map) : GameServer에서 유저의 <id, index> 정보 관리 (0) | 2020.12.13 |