원문 캡처 · sandordargo.com
원문 캡처 · sandordargo.com

C++26은 50개가 넘는 범위 알고리즘에 실행 정책을 추가해 병렬 실행을 지원한다. 다만 병렬 처리는 범위가 임의 접근과 크기 측정을 지원해야 하며, filter_view의 일부 위험한 동작도 함께 개선된다.

P3179R9에 따라 실행 정책을 알고리즘의 첫 번째 인수로 전달한다. C++17에서 제공하던 seq, par, par_unseq, unseq 정책을 사용할 수 있다. for_each, transform, sort, find 등 비수치 알고리즘 대부분이 대상이다. 반면 reduce, transform_reduce 같은 수치 알고리즘과 C++17에 병렬 버전이 없던 push_heap, binary_search 등은 포함되지 않는다.

병렬 범위 알고리즘은 random_access_range와 sized_range를 모두 요구한다. 작업을 나누려면 임의 접근과 범위 크기를 알아야 하기 때문이다. 따라서 기껏해야 양방향 범위인 filter_view나 끝이 정해지지 않은 views::iota를 바로 병렬 처리할 수는 없다. 먼저 범위를 구체화하거나 크기를 제한해야 한다.

프로젝션을 지원하며, 출력 인수도 출력 반복자가 아니라 범위로 받는다. 범위로 출력 크기를 확인할 수 있도록 한 설계다. 병렬 for_each는 함수 객체가 아닌 반복자만 반환한다. 호출 객체는 스레드 사이에서 복사되므로 그 안에 상태를 누적하면 데이터 경합이 생길 수 있다. 결과를 모아야 한다면 출력 범위에 쓰는 transform을 사용해야 한다. 호출 객체는 복사 생성이 가능해야 하며, constexpr 지원은 아직 포함되지 않았다.

P3725R3은 C++20부터 위험 요소로 지적된 filter_view의 문제를 다룬다. 필터 뷰를 reverse와 조합하면 내부의 캐시된 begin()과 상호작용해 정의되지 않은 동작이 발생할 수 있고, 조건을 만족하는 요소를 필터 반복자로 수정해 조건의 참·거짓이 바뀌는 경우에도 정의되지 않은 동작이었다. 또 begin()을 캐시하려면 변경 가능한 상태가 필요해, 필터링된 범위를 const auto&를 받는 함수에 전달할 수 없는 경우가 있었다.

변경안은 기반 범위가 입력 범위(input range)이고 순방향 범위(forward range)는 아닐 때 filter_view에 const begin()과 const end()를 제공한다. 입력 범위는 한 번만 순회하므로 begin()을 캐시할 필요가 없다. 또한 필터 반복자를 통해 요소를 수정하는 동작은 수정된 요소에서 조건자를 다시 평가하지 않는 한 정의된 동작으로 바뀐다.

위험한 조합을 피하는 방법으로는 필터 앞에 views::to_input을 붙이는 패턴을 제안한다. 이를 적용하면 범위가 입력 전용으로 바뀌어 양방향 범위를 요구하는 reverse가 컴파일되지 않는다. 런타임에 정의되지 않은 동작이 발생하는 대신 컴파일 오류로 드러나며, 입력 범위는 begin() 캐시가 필요하지 않아 상수 반복도 가능해진다. views::to_input은 C++26 ranges 연재 1편에서 다뤘다.

그 밖에도 C++26은 basic_const_iterator의 변환 가능성 조정, 프록시 반복자에서 유효한 사용 사례를 거부하던 common_reference 요구 완화, ranges::fold_left 관련 알고리즘 함수 객체 표준화 등을 포함한다. 연속 반복자를 포인터로 바꿔 알고리즘 코드 생성 개선을 허용하고, 뷰 반복자·센티널의 생성자 중 실수로 공개 API가 된 항목을 비공개로 바꾸는 수정도 있다. 각 변경은 P2836R1, P2997R1, P3136R1, P3349R0, P3059R2에 정리돼 있다.

C++26은 C++17의 병렬 알고리즘과 C++20의 범위 기능을 연결하지만, 병렬 실행에는 임의 접근과 크기 측정이 가능한 범위가 필요하다. 이번 변경으로 일부 필터 뷰의 위험한 조합도 런타임의 정의되지 않은 동작 대신 컴파일 단계에서 막을 수 있다.