std :: back_inserter와 함께 std :: transform을 사용하는 것이 유효합니까?


20

Cppreference에는 다음에 대한 예제 코드가 있습니다 std::transform.

std::vector<std::size_t> ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
               [](unsigned char c) -> std::size_t { return c; });

그러나 그것은 또한 말합니다 :

std::transformunary_op또는의 순서대로 적용 할 수있는 것은 아닙니다 binary_op. 시퀀스에 순서대로 함수를 적용하거나 시퀀스의 요소를 수정하는 함수를 적용하려면을 사용하십시오 std::for_each.

이것은 아마도 병렬 구현을 허용하는 것입니다. 그러나 세번째의 파라미터 std::transformA는 LegacyOutputIterator에 대해 다음 사후을 갖는다 ++r:

이 조작 후에 r증분 할 r필요가없고 이전 값의 사본을 더 이상 역 참조 또는 증분 할 필요가 없습니다.

따라서 출력 할당이 순서대로 이루어져야한다고 생각합니다 . 그것들은 단순히 응용 프로그램의 unary_op순서가 잘못되어 임시 위치에 저장되었지만 출력에 순서대로 복사된다는 것을 의미합니까? 그것은 당신이하고 싶은 것 같지 않습니다.

대부분의 C ++ 라이브러리는 실제로 병렬 실행기를 아직 구현하지 않았지만 Microsoft는 아직 구현했습니다. 나는 이것이 관련 코드 라고 확신 하며, 반복적으로 출력 청크에 반복자를 기록하기 위해이 함수 를 호출 한다고 생각 합니다.populate()LegacyOutputIterator

내가 무엇을 놓치고 있습니까?


Godbolt 의 간단한 테스트 는 이것이 문제 라는 것을 보여줍니다. transform병렬화를 사용할지 여부를 결정하는 C ++ 20 및 버전. transform큰 벡터에 실패합니다.
Croolman

6
@Croolman을 (를) 다시 삽입하여 s반복자를 무효화 하므로 코드가 잘못되었습니다 .
Daniel Langr

@DanielsaysreinstateMonica Oh schnitzel 당신이 맞아요. 그것을 조정하고 유효하지 않은 상태로 두었습니다. 나는 나의 의견을 되 찾는다.
Croolman

당신이 사용하는 경우 std::transform수렴 정책을 다음 랜덤 액세스 반복자가 필요합니다 어떤 back_inserter성취 할 수 없다. 인용 된 IMO 부품 문서는 해당 시나리오를 나타냅니다. 설명서의 예제는을 사용합니다 std::back_inserter.
Marek R

@Croolman 병렬 처리를 자동으로 사용하기로 결정 했습니까?
curiousguy

답변:


9

1) 표준의 출력 반복자 요구 사항이 완전히 깨졌습니다. LWG2035를 참조하십시오 .

2) 순전히 출력 반복자와 순전히 입력 소스 범위를 사용하는 경우 알고리즘이 실제로 할 수있는 일은 거의 없습니다. 순서대로 쓰는 것 외에는 선택의 여지가 없습니다. (그러나 가상 구현은 자체 유형을 특수한 경우로 선택할 수 있습니다 std::back_insert_iterator<std::vector<size_t>>. 어떤 구현이 여기에서 원하는지 모르겠지만 그렇게 할 수는 있습니다.)

3) 표준에서 보증을 transform순서대로 적용하는 것은 없습니다 . 우리는 구현 세부 사항을보고 있습니다.

std::transform이 같은 경우에 더 높은 반복자의 강점과 재정렬 작업을 감지 할 수없는 말은하지 않는 경우에만 출력 반복자를 필요로한다. 실제로, 알고리즘은 반복자 강도에 파견 모든 시간 , 그들은 (포인터 또는 벡터 반복자 같은) 특별한 반복자 유형에 대한 특별한 취급이 모든 시간을 .

표준은 특정 주문을 보장하고자 할 때, 어떻게 말하는지 알고 있습니다 ( std::copy'시작 first및 진행 ' 참조 last).


5

보낸 사람 n4385:

§25.6.4 변환 :

template<class InputIterator, class OutputIterator, class UnaryOperation>
constexpr OutputIterator
transform(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperation op);

template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class UnaryOperation>
ForwardIterator2
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 result, UnaryOperation op);

template<class InputIterator1, class InputIterator2, class OutputIterator, class BinaryOperation>
constexpr OutputIterator
transform(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, BinaryOperation binary_op);

template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class ForwardIterator, class BinaryOperation>
ForwardIterator
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator result, BinaryOperation binary_op);

§23.5.2.1.2 back_inserter

template<class Container>
constexpr back_insert_iterator<Container> back_inserter(Container& x);

back_insert_iterator (x)를 리턴합니다.

§23.5.2.1 클래스 템플릿 back_insert_iterator

using iterator_category = output_iterator_tag;

따라서 std::back_inserter병렬 버전의와 함께 사용할 수 없습니다 std::transform. 출력 반복자를 지원하는 버전은 입력 반복기와 함께 소스에서 읽습니다. 입력 반복자는 사전 및 사후 증분 (§23.3.5.2 입력 반복자) 만 가능하고 순차적 ( 즉, 비 병렬) 실행 만 있기 때문에 순서와 출력 반복자 사이에 순서를 유지해야합니다.


2
C ++ 표준의 이러한 정의는 추가 유형의 반복자에 대해 선택된 특수 버전의 알고리즘을 제공하기위한 구현을 피하지 않습니다. 예를 들어, 입력 반복자std::advance 를 취하는 하나의 정의가 있지만 libstdc ++는 양방향 반복기랜덤 액세스 반복 자를위한 추가 버전을 제공 합니다 . 그런 다음 전달 된 반복기 유형에 따라 특정 버전이 실행 됩니다.
Daniel Langr

나는 당신의 의견이 정확하다고 생각하지 않습니다.- ForwardIterator당신이 순서대로 일을해야한다는 것을 의미하지는 않습니다. 그러나 당신은 내가 놓친 것을 강조했다-그들이 사용 ForwardIterator하지 않는 병렬 버전을 위해 OutputIterator.
Timmmm

1
아 맞아, 그래 우리가 동의하는 것 같아
Timmmm

1
이 답변은 실제로 의미하는 바를 설명하기 위해 단어를 추가하면 도움이 될 수 있습니다.
Barry

1
@Barry 일부 단어를 추가했습니다.
Paul Evans

0

그래서 내가 놓친 것은 병렬 버전이 LegacyForwardIterator아니라 LegacyOutputIterator. A LegacyForwardIterator 복사본을 무효화하지 않고 증분 수 있으므로이 명령을 사용하여 비 순차 병렬을 쉽게 구현할 수 std::transform있습니다.

병렬이 아닌 버전을 순서대로 실행 std::transform 해야 한다고 생각합니다 . cppreference가 잘못되었거나 표준은이 요구 사항을 구현하는 다른 방법이 없기 때문에이 요구 사항을 암시 적으로 남겨 둡니다. (Shotgun은 표준을 찾지 못하고 있습니다!)


모든 반복자가 충분히 강한 경우 비 병렬 버전의 변환 순서 잘못 실행될 수 있습니다 . 문제의 예에서 그들은 그렇지 않기 때문에 전문화transform순서대로 이루어져야합니다.
Caleth

LegacyOutputIterator순서대로 사용하도록 강요 하기 때문에 아닙니다 .
Timmmm

std::back_insert_iterator<std::vector<T>>와에 대해 다르게 전문화 할 수 있습니다 std::vector<T>::iterator. 첫 번째 순서대로해야합니다. 두 번째는 그런 제한이 없습니다
Caleth

아 잠깐만 요. 무슨 의미인지 알겠습니다 LegacyForwardIterator. 비 병렬에 a를 전달하면 transform순서가 잘못된 것을 전문화 할 수 있습니다. 좋은 지적.
Timmmm

0

나는 변환 이 순서대로 처리 될 것이라고 믿습니다 . std::back_inserter_iterator출력 반복자 (그것의 iterator_category부재 형이 별명이다 std::output_iterator_tag)에 따라 [back.insert.iterator] .

따라서, std::transform이없는 다른 옵션 콜 회원에게보다 다음 반복으로 진행하는 방법에 대한 operator++상의 result매개 변수를.

물론 이것은 실행 정책이없는 과부하에만 유효합니다 std::back_inserter_iterator( 포워드 반복자가 아님).


BTW, 나는 cppreference의 따옴표로 논쟁하지 않을 것입니다. 거기에 언급 된 내용은 종종 부정확하거나 간결합니다. 이러한 경우 C ++ 표준을 보는 것이 좋습니다. 에 std::transform대해 작업 순서에 대한 인용문이없는 경우


"C ++ 표준. std :: transform과 관련하여 작업 순서에 대한 견적이 없습니다." 순서가 언급되지 않았으므로 지정되지 않습니까?
HolyBlackCat

@HolyBlackCat 명시 적으로 지정되지 않았지만 출력 반복자에 의해 부과됩니다. 출력 반복자를 사용하면 일단 증가한 후에는 이전 반복기 값을 역 참조 할 수 없습니다.
Daniel Langr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.