C++ Ranges 파이프라인·뷰 | "함수형 프로그래밍" C++20 가이드

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 알고리즘 쓰기