C++23에서는 범위(Range) 라이브러리를 더욱 유연하고 표준화된 방식으로 다룰 수 있도록 하는 새로운 뷰(View) 어댑터들이 다수 추가되었습니다. 그중 하나인 std::views::common는 범위를 일반 범위(common_range)로 변환하여, 반복자와 센티넬 타입 불일치 문제를 해결하고, 다른 범위 기반 알고리즘과의 호환성을 개선하는 뷰 어댑터입니다. 이를 통해 범위를 다룰 때 흔히 발생하는 반복자-센티넬 타입 차이로 인한 번거로움을 줄이고, 코드 가독성과 유지보수성을 높일 수 있습니다.
이번 글에서는 std::views::common의 개념과 사용법, 그리고 이전 방식과 비교하여 어떤 점이 개선되었는지 알아보겠습니다.
std::views::common란 무엇인가요?
C++20 범위 라이브러리에서 common_range 개념은 반복자와 센티넬 타입이 동일한, 즉 범위의 끝을 같은 타입으로 표현하는 범위를 뜻합니다. 하지만 모든 범위가 common_range는 아닙니다. 예를 들어, std::views::iota나 std::views::take_while 등의 일부 뷰는 끝을 다른 타입의 센티넬로 표현하므로, 반복자와 센티넬이 서로 다른 타입일 수 있습니다.
std::views::common은 이러한 범위를 강제로 common_range로 변환하여, 이후 알고리즘에서 end() 반복자를 편리하게 다룰 수 있도록 합니다. 즉, common 뷰 어댑터를 적용하면 반복자와 센티넬 타입 불일치 문제를 깔끔하게 해소하고, 다른 뷰나 알고리즘과 호환성을 확보할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++20에서는 반복자-센티넬 타입이 다른 범위를 다룰 때 수동으로 std::ranges::common_view를 적용하거나, 다른 방법을 통해 iterator와 sentinel 타입을 맞추는 추가 작업이 필요했습니다.
예제: 기존 방식(C++20까지)
#include <ranges>
#include <iostream>
// iota_view는 기본적으로 common_range가 아님
int main() {
auto rng = std::views::iota(0, 10);
// rng는 [0..9] 범위를 제공하나, iterator와 sentinel 타입이 다를 수 있음
// 예를 들어, 특정 알고리즘이 common_range를 기대한다면?
// 이 경우 std::ranges::common_view를 직접 감싸는 등의 추가 작업 필요
return 0;
}
- 문제점: common_range를 기대하는 알고리즘 사용 시 별도 래퍼나 변환 필요.
C++23의 std::views::common 사용 예제
#include <ranges>
#include <iostream>
int main() {
auto rng = std::views::iota(0, 10); // [0..9] 범위
// iota_view에 common 적용
auto common_rng = rng | std::views::common;
// 이제 common_rng는 common_range이므로, 반복자와 센티넬 타입이 동일
// 이를 통해 standard 알고리즘이나 다른 뷰 조합에서 iterator/sentinel 불일치 문제 해결
for (int x : common_rng) {
std::cout << x << ' ';
}
// 출력: 0 1 2 3 4 5 6 7 8 9
return 0;
}
- std::views::common을 한 번 호출하는 것만으로 반복자-센티넬 타입 불일치를 해결.
- 다른 알고리즘이나 뷰와 연계 시 편리.
어떻게 좋아졌나요?
- 코드 간결성: 수동으로 std::ranges::common_view를 감싸는 등의 번거로운 작업 없이 한 번의 파이프라인 호출로 common_range 확보.
- 가독성 향상: common이라는 이름에서 바로 의도를 파악할 수 있으며, 범위 파이프라인 내에서 간단히 적용 가능.
- 유연성 증대: 다양한 범위에서 iterator/sentinel 타입 문제를 손쉽게 해결, 다른 알고리즘과 쉽게 결합.
- 표준화된 방식: 사용자 정의 래퍼 필요 없이 표준 라이브러리에서 지원.
다른 뷰와의 조합 예시
#include <ranges>
#include <iostream>
int main() {
// iota_view -> filter -> common
auto rng = std::views::iota(0, 20)
| std::views::filter([](int x){ return x % 2 == 0; }) // 짝수만
| std::views::common; // common_range로 변환
// 이제 rng는 common_range, 다른 알고리즘 사용 시 iterator/sentinel 문제 없음
for (int x : rng) {
std::cout << x << ' '; // 출력: 0 2 4 6 8 10 12 14 16 18
}
return 0;
}
- common을 통해 필터링 후에도 common_range 확보, 추후 알고리즘 적용 시 편리.
주의 사항
- 성능 및 비용: common 뷰는 iterator와 sentinel을 동일하게 처리하기 위한 래핑을 수행. 일반적으로 큰 오버헤드는 없지만, 성능에 민감한 경우 프로파일링 필요.
- C++23 지원 여부: C++23 기능이므로, 지원하는 컴파일러와 표준 라이브러리 필요.
요약
C++23의 std::views::common는 iterator와 sentinel 타입이 다른 범위를 common_range로 변환하여, 다른 범위 기반 알고리즘이나 뷰와 더욱 쉽게 연계할 수 있도록 해줍니다. 이를 통해 기존에 separate 변환이나 래퍼를 사용해야 했던 번거로움을 줄이고, 범위 파이프라인 내에서 iterator/sentinel 불일치 문제를 단 한 번의 호출로 해결할 수 있습니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] std::in_place_stop_source와 std::in_place_stop_token (0) | 2024.12.12 |
---|---|
[C++23 새기능 소개] std::views::repeat와 std::views::repeat_n (1) | 2024.12.12 |
[C++23 새기능 소개] std::ranges::drop_last, std::ranges::drop_last_while (1) | 2024.12.12 |
[C++23 새기능 소개] std::byteswap (0) | 2024.12.12 |
C++20과 C++23을 활용한 “파이썬스러운” API 구현 #14: groupby, permutations, product (0) | 2024.12.12 |