C++ Ranges 파이프라인·뷰 | "함수형 프로그래밍" C++20 가이드
이 글의 핵심
C++ Ranges에 대해 정리한 개발 블로그 글입니다. #include <ranges> #include <vector> #include <iostream> namespace rng = std::ranges; namespace vw = std::views;
기본 사용법
#include <ranges>
#include <vector>
#include <iostream>
namespace rng = std::ranges;
namespace vw = std::views;
int main() {
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 짝수만 필터링
auto even = v | vw::filter( { return x % 2 == 0; });
for (int x : even) {
cout << x << " "; // 2 4 6 8 10
}
}
파이프라인
vector<int> v = {1, 2, 3, 4, 5};
// 여러 연산 체이닝
auto result = v
| vw::filter( { return x % 2 == 0; })
| vw::transform( { return x * x; });
for (int x : result) {
cout << x << " "; // 4 16
}
주요 Views
filter
auto positive = v | vw::filter( { return x > 0; });
transform
auto squared = v | vw::transform( { return x * x; });
take / drop
auto first5 = v | vw::take(5); // 처음 5개
auto skip3 = v | vw::drop(3); // 3개 건너뛰기
reverse
auto reversed = v | vw::reverse;
split
string s = "hello world test";
auto words = s | vw::split(' ');
for (auto word : words) {
cout << string(word.begin(), word.end()) << endl;
}
join
vector<vector<int>> nested = {{1,2}, {3,4}, {5,6}};
auto flattened = nested | vw::join;
for (int x : flattened) {
cout << x << " "; // 1 2 3 4 5 6
}
실전 예시
예시 1: 데이터 처리 파이프라인
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
namespace vw = std::views;
struct Person {
string name;
int age;
double salary;
};
int main() {
vector<Person> people = {
{"Alice", 25, 50000},
{"Bob", 30, 60000},
{"Charlie", 35, 70000},
{"David", 28, 55000}
};
// 30세 이상, 급여 60000 이상, 이름만 추출
auto result = people
| vw::filter( { return p.age >= 30; })
| vw::filter( { return p.salary >= 60000; })
| vw::transform( { return p.name; });
for (const auto& name : result) {
cout << name << endl; // Bob, Charlie
}
}
예시 2: 문자열 처리
#include <ranges>
#include <string>
#include <cctype>
namespace vw = std::views;
int main() {
string text = "Hello World 123";
// 대문자만 추출
auto uppercase = text
| vw::filter( { return isupper(c); });
for (char c : uppercase) {
cout << c; // HW
}
cout << endl;
// 숫자만 추출하고 정수로 변환
auto digits = text
| vw::filter( { return isdigit(c); })
| vw::transform( { return c - '0'; });
for (int d : digits) {
cout << d << " "; // 1 2 3
}
}
예시 3: 무한 범위
#include <ranges>
namespace vw = std::views;
int main() {
// 무한 수열
auto naturals = vw::iota(1); // 1, 2, 3, ...
// 처음 10개만
auto first10 = naturals | vw::take(10);
for (int x : first10) {
cout << x << " "; // 1 2 3 ... 10
}
cout << endl;
// 짝수만 10개
auto evenNumbers = naturals
| vw::filter( { return x % 2 == 0; })
| vw::take(10);
for (int x : evenNumbers) {
cout << x << " "; // 2 4 6 ... 20
}
}
예시 4: 중첩 범위 처리
#include <ranges>
#include <vector>
namespace vw = std::views;
int main() {
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 평탄화하고 짝수만
auto result = matrix
| vw::join
| vw::filter( { return x % 2 == 0; });
for (int x : result) {
cout << x << " "; // 2 4 6 8
}
}
지연 평가 (Lazy Evaluation)
vector<int> v = {1, 2, 3, 4, 5};
// 지연 평가: 아직 실행 안됨
auto pipeline = v
| vw::filter( {
cout << "filter " << x << endl;
return x % 2 == 0;
})
| vw::transform( {
cout << "transform " << x << endl;
return x * 2;
});
cout << "파이프라인 생성 완료" << endl;
// 여기서 실행됨
for (int x : pipeline) {
cout << "결과: " << x << endl;
}
ranges 알고리즘
#include <ranges>
#include <algorithm>
namespace rng = std::ranges;
vector<int> v = {3, 1, 4, 1, 5};
// 정렬
rng::sort(v);
// 검색
auto it = rng::find(v, 3);
// 변환
rng::transform(v, v.begin(), { return x * 2; });
// 복사
vector<int> v2(v.size());
rng::copy(v, v2.begin());
커스텀 View
template<typename Range>
auto evenOnly(Range&& range) {
return forward<Range>(range)
| vw::filter( { return x % 2 == 0; });
}
int main() {
vector<int> v = {1, 2, 3, 4, 5};
for (int x : evenOnly(v)) {
cout << x << " "; // 2 4
}
}
자주 발생하는 문제
문제 1: 댕글링 참조
// ❌ 위험
auto getEvens() {
vector<int> v = {1, 2, 3, 4, 5};
return v | vw::filter( { return x % 2 == 0; });
} // v 소멸, 댕글링 참조!
// ✅ 안전
vector<int> getEvens() {
vector<int> v = {1, 2, 3, 4, 5};
auto filtered = v | vw::filter( { return x % 2 == 0; });
return vector<int>(filtered.begin(), filtered.end());
}
문제 2: 성능 오해
// ❌ 매번 재계산
auto pipeline = v | vw::filter(...) | vw::transform(...);
for (int i = 0; i < 10; i++) {
for (int x : pipeline) { // 매번 필터링+변환
// ...
}
}
// ✅ 한 번만 계산
vector<int> result(pipeline.begin(), pipeline.end());
for (int i = 0; i < 10; i++) {
for (int x : result) {
// ...
}
}
문제 3: 컴파일러 지원
// GCC 10+, Clang 13+, MSVC 2019+ 필요
#include <ranges>
// 이전 버전에서는 range-v3 라이브러리 사용
// #include <range/v3/all.hpp>
Ranges vs 기존 알고리즘
vector<int> v = {1, 2, 3, 4, 5};
// 기존 방식
vector<int> result;
copy_if(v.begin(), v.end(), back_inserter(result),
{ return x % 2 == 0; });
transform(result.begin(), result.end(), result.begin(),
{ return x * 2; });
// Ranges 방식
auto result2 = v
| vw::filter( { return x % 2 == 0; })
| vw::transform( { return x * 2; });
FAQ
Q1: Ranges는 왜 사용하나요?
A:
- 파이프라인 스타일 (읽기 쉬움)
- 지연 평가 (효율적)
- 조합 가능
- 타입 안전
Q2: 성능은?
A: 지연 평가로 불필요한 계산을 피하고, 인라인화로 오버헤드가 거의 없습니다.
Q3: 기존 알고리즘과 혼용 가능?
A: 네, ranges::begin/end를 사용하면 됩니다.
Q4: range-v3 vs 표준 Ranges?
A: 표준 Ranges는 range-v3 기반입니다. C++20 이상이면 표준을 사용하세요.
Q5: 컴파일 시간은?
A: 템플릿 사용으로 약간 증가할 수 있지만, 대부분 무시할 수 있습니다.
Q6: Ranges 학습 리소스는?
A:
- cppreference.com
- “C++20 Ranges” (Tristan Brindle)
- range-v3 문서
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Ranges | “범위 라이브러리” 가이드
- C++ Views | “뷰” 가이드
- C++ Ranges Views와 파이프라인 | 지연 연산으로 효율적으로 다루기 [#25-2]
관련 글
- C++20 Ranges | begin/end 반복 탈출하고 ranges 알고리즘 쓰기