[C++23 새기능 소개] std::ranges::to 함수 템플릿

C++23에서는 범위(Range) 라이브러리를 더욱 풍성하게 만들기 위해 std::ranges::to 함수 템플릿이 도입되었습니다. 이번 글에서는 std::ranges::to의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.

std::ranges::to란 무엇인가요?

C++20에서 범위(Range) 라이브러리가 도입되면서, 파이프라인 형태로 뷰(View)나 어댑터(Adapter)를 조합하여 데이터를 변환, 필터링, 슬라이싱하는 것이 가능해졌습니다. 하지만 변환된 범위를 최종적으로 컨테이너로 재수집하는 과정은 여전히 수동으로 작성해야 했습니다.

 

C++23의 std::ranges::to는 이러한 변환 결과를 간단한 한 줄의 코드로 원하는 컨테이너로 모아주는 유틸리티 함수 템플릿입니다. 즉, 다양한 뷰나 변환을 거친 범위를 std::vector, std::list 등으로 손쉽게 변환할 수 있습니다.

이전 버전에서는 어떻게 했나요?

C++20 범위 라이브러리를 활용하면 다음과 같은 변환이 가능했습니다.

예제: C++20 방식

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

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 짝수만 필터링하고 제곱한 뒤, 다시 vector로 모으기 (C++20)
    auto even_squares_view = numbers
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * x; });

    // 범위를 다시 vector로 만들기 위해서는 직접 삽입 필요
    std::vector<int> result;
    std::ranges::copy(even_squares_view, std::back_inserter(result));

    for (int v : result) {
        std::cout << v << ' ';
    }

    return 0;
}
  • 문제점:
    • 범위를 거쳐 나온 결과를 컨테이너로 재수집하기 위해 std::ranges::copy를 사용하거나, 직접 insert iterator를 사용해야 했습니다.
    • 한 줄로 간단히 변환하기 어렵고, 중간에 insert iterator나 copy를 작성하는 번거로움이 있었습니다.

C++23의 std::ranges::to를 사용한 개선

std::ranges::to를 사용하면 변환 결과를 원하는 컨테이너 타입으로 직접 변환할 수 있습니다.

예제: std::ranges::to 사용

#include <vector>
#include <ranges>
#include <iostream>
#include <utility> // for std::piecewise_construct if needed
#include <string>

// C++23의 std::ranges::to를 사용하기 위해서는 구현체 지원 필요 (예: libstdc++에서 지원 예정)

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    auto even_squares = numbers
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * x; })
        | std::ranges::to<std::vector>(); // 결과를 vector<int>로 바로 변환

    for (int v : even_squares) {
        std::cout << v << ' ';
    }
    // 출력: 4 16

    return 0;
}
  • std::ranges::to<std::vector>()를 통해 변환된 범위를 바로 std::vector로 얻을 수 있습니다.
  • 코드가 훨씬 간결하고 직관적으로 바뀌었습니다.

어떻게 좋아졌나요?

  • 코드 간소화: std::ranges::to를 사용하면 컨테이너 변환 로직을 한 줄로 처리할 수 있습니다.
  • 범용성: 다양한 컨테이너(std::vector, std::list, std::string 등)로 변환할 수 있으므로, 코드 재사용성과 유연성이 증대됩니다.
  • 가독성 향상: 파이프라인 형태의 변환 후 결과 컨테이너를 얻는 과정이 명확해져 코드 의도를 쉽게 파악할 수 있습니다.

상세한 예제와 비교

1. 다른 컨테이너 타입으로 변환

#include <list>
#include <ranges>
#include <string>
#include <iostream>

int main() {
    std::string text = "C++23 Ranges to Container";

    auto uppercase_letters = text
        | std::views::filter([](unsigned char c) { return std::isalpha(c); })
        | std::views::transform([](unsigned char c) { return std::toupper(c); })
        | std::ranges::to<std::list>(); // 결과를 list<char>로 변환

    for (char c : uppercase_letters) {
        std::cout << c;
    }
    // 출력: CPLUSPLUSRANGESTOCONTAINER

    return 0;
}
  • std::ranges::to<std::list>()를 사용하여 문자 범위를 std::list<char>로 바로 변환할 수 있습니다.

2. 사용자 정의 컨테이너로 변환 (컨셉 충족 시)

#include <ranges>
#include <iostream>

// 간단한 사용자 정의 컨테이너
template<typename T>
struct MyContainer {
    std::vector<T> data_;
    void push_back(const T& value) { data_.push_back(value); }
    // ... 컨테이너 요구사항 충족 시
};

int main() {
    std::vector<int> numbers = {10, 20, 30};

    auto result = numbers
        | std::views::transform([](int x){ return x+1; })
        | std::ranges::to<MyContainer<int>>(); // MyContainer<int>로 변환 가능

    for (auto v : result.data_) {
        std::cout << v << ' '; // 11 21 31
    }

    return 0;
}
  • std::ranges::to는 컨테이너 요구사항(컨셉)을 만족하는 임의의 타입에도 적용할 수 있어, 유연한 확장성을 제공합니다.

주의 사항

  • 컴파일러 및 라이브러리 지원: std::ranges::to는 C++23 기능이므로, 해당 기능을 지원하는 컴파일러와 표준 라이브러리가 필요합니다. (일부 구현체에서 아직 완벽히 지원하지 않을 수 있음)
  • 적절한 컨테이너 선택: 변환 대상 컨테이너가 특정 생성자나 삽입 인터페이스를 요구할 수 있으므로, 컨테이너 요구사항을 충족하는지 확인해야 합니다.

요약

C++23의 std::ranges::to는 범위 라이브러리를 통해 변환, 필터링, 조합한 뷰를 손쉽게 원하는 컨테이너로 변환하는 기능을 제공합니다. 이전에는 std::ranges::copy나 삽입 반복자 등을 통해 수동으로 변환해야 했지만, 이제는 한 줄로 컨테이너 변환을 수행할 수 있어 코드 가독성, 유연성, 생산성이 크게 향상됩니다.

 

 

참고 자료:

반응형