본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : std::ranges (20)

by snowoods 2025. 10. 11.

Modern C++

 

std::ranges

 

개요

std::ranges는 C++20에 도입된 주요 기능으로, 컬렉션(컨테이너)을 다루는 방식을 혁신적으로 개선합니다. 기존의 반복자(iterator) 기반의 알고리즘을 파이프라이닝(pipelining) 가능한 '뷰(view)'와 '어댑터(adapter)'를 통해 더 직관적이고 가독성 높게 사용할 수 있도록 합니다. 이를 통해 복잡한 데이터 처리 로직을 간결하게 표현할 수 있습니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++20 : std::ranges, std::views, concepts와 함께 도입되었습니다.

 

내용 설명

std::ranges 라이브러리는 크게 세 가지 요소로 구성됩니다.

  • Ranges (범위): 반복 가능한 요소의 시퀀스를 나타내는 개념입니다. std::vector, std::list와 같은 컨테이너는 범위가 될 수 있습니다.
  • Views (뷰): 범위에 대한 경량(lightweight) 어댑터입니다. 뷰는 데이터를 소유하지 않고, 원본 데이터를 변환하거나 필터링하는 방법을 정의합니다. 뷰는 조합(composable)이 가능하여 여러 변환을 파이프라인처럼 연결할 수 있습니다. std::views 네임스페이스에는 filter, transform, take, drop 등 다양한 뷰 어댑터가 포함되어 있습니다.
  • Algorithms (알고리즘): 기존의 <algorithm> 헤더에 있던 알고리즘들이 std::ranges 버전으로 오버로드되었습니다. 이 알고리즘들은 반복자 쌍 대신 범위를 직접 인자로 받을 수 있어 코드가 간결해집니다.

std::ranges의 가장 큰 장점 중 하나는 파이프라인 연산자 |를 사용하여 뷰를 연결할 수 있다는 점입니다. 이를 통해 데이터 처리 흐름을 왼쪽에서 오른쪽으로 자연스럽게 읽을 수 있습니다.

예를 들어, numbers | std::views::filter(...) | std::views::transform(...)와 같은 코드는 numbers 컬렉션에 대해 필터링을 적용한 후, 그 결과에 변환을 적용하는 과정을 명확하게 보여줍니다.

 

예제 코드

#include <algorithm>
#include <iostream>
#include <ranges>
#include <string_view>
#include <vector>

auto print = [](auto const &view) {
    std::cout << "{ ";
    for (const auto element : view)
    {
        std::cout << element << ' ';
    }
    std::cout << "} ";
};

int main()
{
    // Drop, Take, Transform, Filter
    auto numbers1 = std::vector<int>{1, 2, 3, 4, 5, 6};

    auto results1 = std::views::reverse(numbers1) | std::views::drop(2) |
                    std::views::take(3) |
                    std::views::transform([](const auto n) { return n * 2; }) |
                    std::views::filter([](const auto n) { return n % 2 == 0; });

    for (auto v : numbers1)
        std::cout << v << " ";
    std::cout << std::endl;

    for (auto v : results1)
        std::cout << v << " ";
    std::cout << std::endl;

    // Filter, Transform
    auto numbers2 = std::vector<int>{1, 2, 3, 4, 5, 6};
    auto results2 = numbers2 |
                    std::views::filter([](int n) { return n % 2 == 0; }) |
                    std::views::transform([](int n) { return n * 2; });

    for (auto v : results2)
        std::cout << v << " ";
    std::cout << std::endl;

    // Split
    constexpr auto hello = std::string_view{"Hello C++ 20 !"};
    auto res = hello | std::ranges::views::split(' ');
    std::cout << "substrings: ";
    std::ranges::for_each(res, print);

    return 0;
}

 

실행 결과

1 2 3 4 5 6 
8 6 4 
4 8 12 
substrings: { H e l l o } { C + + } { 2 0 } { ! }

 

활용팁

  • 지연 평가(Lazy Evaluation): std::ranges의 뷰는 지연 평가됩니다. 즉, 실제 요소가 필요할 때까지 계산이 수행되지 않습니다. 이는 불필요한 계산을 줄여 성능을 향상시킬 수 있습니다.
  • 가독성 향상: 복잡한 STL 알고리즘 호출 체인을 파이프라인으로 대체하여 코드의 가독성과 유지보수성을 크게 향상시킬 수 있습니다.
  • 무한 시퀀스: std::views::iota와 같은 뷰를 사용하면 무한한 데이터 시퀀스를 표현하고 필요한 만큼만 처리할 수 있습니다.
  • 조합성: 다양한 뷰를 조합하여 복잡한 데이터 처리 파이프라인을 쉽게 구축할 수 있습니다. 예를 들어, 필터링, 변환, 정렬, 일부 요소 선택 등의 작업을 하나의 표현식으로 연결할 수 있습니다.