Std :: dönüşümü std :: back_inserter ile kullanmak geçerli midir?


20

Cppreference için şu örnek kod vardır 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; });

Ancak şunu da söylüyor:

std::transformunary_opveya 'nın siparişe göre uygulanmasını garanti etmez binary_op. Bir diziye sırayla bir işlev uygulamak veya bir dizinin öğelerini değiştiren bir işlevi uygulamak için kullanın std::for_each.

Bu muhtemelen paralel uygulamalara izin vermek içindir. Bununla birlikte, üçüncü parametresi aşağıdaki son koşula sahip olan std::transformbir a parametresidir :LegacyOutputIterator++r

Bu işlemin rartırılabilir olması gerekmez ve önceki değerinin herhangi bir kopyasının rartık silinemez veya artırılabilir olması gerekmez.

Bu yüzden çıkış atama geliyor bana gereken sırayla olur. Uygulamanın düzensiz unary_opolabileceği ve geçici bir yerde saklanabileceği, ancak sırayla çıktıya kopyalanabileceği anlamına mı geliyor? Bu hiç yapmak isteyeceğiniz bir şey gibi gelmiyor.

Çoğu C ++ kütüphanesi aslında paralel yürütücüler uygulamamıştır, ancak Microsoft uygulamıştır. Ben eminim bu ilgili kod ve ben düşünüyorum o çağıran bu populate()fonksiyonu elbette çünkü yapmak için geçerli bir şey değildir çıktı, parçaları rekor Yineleyicilerin için LegacyOutputIteratorkopyalarını artırarak geçersiz edilebilir.

Neyi kaçırıyorum?


Godbolt'ta yapılan basit bir test bunun bir sorun olduğunu gösteriyor. C ++ 20 ve transformsürümü ile paralelliği kullanıp kullanmayacağınıza karar verir. transformBüyük vektörler için başarısız olur.
Croolman

6
@Croolman Tekrar eklediğiniz için syineleyicileri geçersiz kılan kodunuz yanlış .
Daniel Langr

Oh Schnitzel haklısın. Tweaking ve geçersiz bir durumda bıraktı. Yorumumu geri alıyorum.
Croolman

Eğer kullanırsanız std::transformzorla alma politikası ile daha sonra rasgele erişim yineleyici gereklidir hangi back_inserteryerine getiremez. IMO tarafından belirtilen parça dokümantasyonu bu senaryoyu ifade eder. Doküman kullanımındaki örneğe dikkat edin std::back_inserter.
Marek R

@Croolman Paralelliğin otomatik olarak kullanılmasına karar verir mi?
curiousguy

Yanıtlar:


9

1) Standarttaki çıkış yineleyici gereksinimleri tamamen bozulmuştur. Bkz. LWG2035 .

2) Tamamen çıkış yineleyicisi ve tamamen giriş kaynağı aralığı kullanıyorsanız, algoritmanın pratikte yapabileceği çok az şey vardır; sırayla yazmaktan başka seçeneği yok. (Bununla birlikte, varsayımsal bir uygulama, kendi türlerini özel olarak ele almayı seçebilir, örneğin std::back_insert_iterator<std::vector<size_t>>; Herhangi bir uygulamanın neden burada yapmak isteyeceğini anlamıyorum, ancak buna izin verilir.)

3) Standartlarda transform, dönüşümleri sırayla uygulayan hiçbir garanti yoktur . Bir uygulama ayrıntısına bakıyoruz.

Yani std::transformbu tür durumlarda daha yüksek yineleyici güçlü ve sipariş işlemleri tespit edemez anlamına gelmez, sadece çıkış yineleyicinızı gerektirir. Gerçekten de, algoritmalar yineleyici gücüne sevk her zaman ve onlar (işaretçiler veya vektör adım adım elde gibi) özel yineleyici türleri için özel işlem var her zaman .

Standart belirli bir siparişi garanti altına almak istediğinde, bunu nasıl söyleyeceğini bilir (bkz. std::copy'Den başlayarak " firstdevam last").


5

Gönderen n4385:

§25.6.4 Dönüşüm :

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 arka_aşağı

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

Döndürür: back_insert_iterator (x).

§23.5.2.1 Sınıf şablonu back_insert_iterator

using iterator_category = output_iterator_tag;

Yani std::back_inserterparalel sürümleri ile kullanılamaz std::transform. Çıkış yineleyicilerini destekleyen sürümler giriş yineleyicilerle kaynaklarından okunur. Giriş yineleyiciler yalnızca artırılmadan önce ve sonra artırılabildiğinden (§23.3.5.2 Giriş yineleyiciler) ve yalnızca ardışık ( yani paralel olmayan) yürütme olduğundan, sıra ile çıkış yineleyicisi arasında sıralama korunmalıdır.


2
C ++ Standardı'ndaki bu tanımların, ek yineleyiciler için seçilen algoritmaların özel sürümlerini sağlamak için uygulamalardan kaçınmadığını unutmayın. Örneğin, girdi yineleyicileristd::advance alan tek bir tanım vardır , ancak libstdc ++ çift ​​yönlü yineleyiciler ve rasgele erişim yineleyiciler için ek sürümler sağlar . Belirli sürüm daha sonra geçirilen yineleyicinin türüne göre yürütülür .
Daniel Langr

ForwardIteratorYorumunuzun doğru olduğunu düşünmüyorum . Ama ben cevapsız şeyi sermiştir - kullandıkları paralel sürümleri için ForwardIteratordeğil OutputIterator.
Timmmm

1
Ah doğru, evet bence hemfikiriz.
Timmmm

1
Bu cevap, aslında ne anlama geldiğini açıklamak için bazı kelimeler eklemekten yararlanabilir.
Barry

1
@Barry Bazı kelimeler eklendi, her türlü geri bildirim çok takdir edildi.
Paul Evans

0

Yani kaçırdığım şey paralel sürümlerin alması LegacyForwardIteratordeğil LegacyOutputIterator. A LegacyForwardIterator , kopyaları geçersiz kılmadan artırılabilir, bu nedenle sıra dışı bir paralel uygulamak için bunu kullanmak kolaydır std::transform.

Ben olmayan paralel versiyonları düşünmek std::transform gerekir in-order yürütülecek. Ya bu konuda yanlışlık ya da muhtemelen standart bu gereksinimi örtülü bırakır çünkü onu uygulamanın başka bir yolu yoktur. (Av tüfeği bulmak için standartta göletmiyor!)


Paralel olmayan dönüşüm sürümleri, tüm yineleyiciler yeterince güçlü ise sıra dışı çalışabilir . Böylece söz konusu örnekte Değilse o uzmanlaşma arasında transformin-order olmalıdır.
Caleth

Hayır olmayabilir, çünkü LegacyOutputIteratorsizi sırayla kullanmaya zorlar.
Timmmm

Bu için farklı uzmanlaşabilir std::back_insert_iterator<std::vector<T>>ve std::vector<T>::iterator. İlki düzenli olmalı. İkincisi böyle bir kısıtlamaya sahip değil
Caleth

Ah ne demek istediğimi anlıyorum - eğer LegacyForwardIteratorparalel olmayan bir hale gelirseniz transform, bunu düzensiz yapan bir uzmanlık olabilir. İyi bir nokta.
Timmmm

0

Dönüşümün sırayla işlenmesi garanti edildiğine inanıyorum . [back.insert.iterator] 'a göre std::back_inserter_iteratorbir çıkış yineleyicisidir ( iterator_categoryüye türü bir takma addır) .std::output_iterator_tag

Sonuç olarak, std::transformsahip başka bir seçenek çağrı üyesine daha sonraki yinelemeye atılacak adımlar konusunda operator++ilgili resultparametre.

Tabii ki, bu sadece kullanılamayacağı yürütme politikası olmayan aşırı yükler için geçerlidir std::back_inserter_iterator(bir yönlendirme yineleyicisi değildir ).


BTW, cppreference'den alıntılarla tartışmam. Buradaki ifadeler genellikle kesin değildir veya basitleştirilmiştir. Bu gibi durumlarda, C ++ Standardına bakmak daha iyidir. İçin ilgili yerlerde, std::transform, operasyonların sipariş hakkında hiçbir alıntı yoktur.


"C ++ Standardı. Nerede, std :: transform ile ilgili olarak, işlemlerin sırası ile ilgili bir alıntı yoktur" Siparişten bahsedilmediğinden, belirtilmemiş mi?
HolyBlackCat

@HolyBlackCat Açıkça belirtilmemiş ancak çıktı yineleyici tarafından dayatılmıştır. Çıktı yineleyicilerde, bir kez artırdığınızda, önceki yineleyici değerlerinden herhangi birini kaldıramayacağınızı unutmayın.
Daniel Langr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.