2013-02-12 8 views
26

This SO question ha scatenato una discussione su std::generate e le garanzie fatte dalla norma. In particolare, è possibile utilizzare gli oggetti funzione con stato interno e fare affidamento su generate(it1, it2, gen) per chiamare gen(), archiviare il risultato in *it, chiamare di nuovo gen(), archiviare in *(it + 1) ecc. Oppure può avviarsi sul retro, ad esempio?Formulazione standard C++: "attraverso tutti gli iteratori nell'intervallo" implica sequenzialità?

Lo standard (n3337, §25.3.7/1) dice:

Effetti: Il primo algoritmo richiama l'oggetto funzione gen e assegna il valore restituito GEN attraverso tutti gli iteratori nell'intervallo [first,last) . Il secondo algoritmo richiama la funzione oggetto gen e assegna il valore restituito di gen attraverso tutti gli iteratori nell'intervallo [first,first + n) se n è positivo, altrimenti non fa nulla.

Sembra che nessun ordinamento è garantito, soprattutto perché altri paragrafi hanno forte formulazione, ad esempio std::for_each (Effetti: Si applica f al risultato di dereferenziazione ogni iteratore nella gamma [first,last), a partire dalla prima e procedendo a last - 1 . Se stiamo prendendo questa lettera, si garantisce solo per iniziare a first e terminerà alle last anche se - non ci sono garanzie sul ordinamento in mezzo).

Ma: Sia Microsoft's e Apache's C++ standard library entrambi danno esempi sulle loro pagine di documentazione che richiedono la valutazione di essere sequenziale. E sia libC++ (in algorithm) che libstdC++ (in bits/stl_algo.h) lo implementano in questo modo. Inoltre, si perde un sacco di potenziali applicazioni per generate senza questa garanzia.

Condivide la formulazione attuale implica sequenzialità? In caso contrario, si trattava di una svista dei membri del comitato o intenzionale?

(Sono ben consapevole che non ci sono molte persone che possono fornire risposte penetranti a questa domanda senza limitarsi a speculare o discutere, ma a mio modesto parere, questo non rende questa domanda "non costruttiva" come da linee guida SO .)


Grazie a @juanchopanza per sottolineare questo problema e mi riferimento al paragrafo relativo for_each.

+1

Non credo 'generate()' è molto utile se non è sequenziale. –

+8

Credo che la vaghezza sia considerevolmente sollevata quando viene presa nel contesto dell'iteratore richiesto * classe * la funzione template lo richiede; ** 'template ' **. Poiché sia ​​il primo che l'ultimo sono solo in avanti, salvo per impilarli tutti in un vettore o in un costrutto vettoriale, quindi non saltare in sequenza sulla sequenza, non hai altra scelta che iniziare all'inizio e arrivare alla fine. – WhozCraig

+0

@WhozCraig Heh, l'ho assolutamente perso. A mio parere, questa è una risposta perspicace e dovresti postarla come tale. Ciononostante, sarei comunque interessato a sentire da persone vicine al comitato o agli implementatori delle biblioteche riguardo ai loro pensieri in merito. – us2012

risposta

7

Nella discussione di LWG475, std::for_each viene confrontato con std::transform. Si noti che "transform non garantisce l'ordine in cui è chiamato il suo oggetto funzione". Quindi, sì, il comitato è consapevole della mancanza di garanzie sequenziali nello standard.

Non v'è alcun obbligo opposto per comportamento non sequenziale o, in modo Microsoft e Apache sono liberi di utilizzare una valutazione sequenziale.

+0

Molto interessante (anche se un po 'sconcertante, perché per 'generate' /' generate_n' in particolare, penserei che i vantaggi del mandare un comportamento sequenziale superano di gran lunga i potenziali svantaggi). Grazie! – us2012

5

Ovunque lo standard non specifica un ordinamento su un algoritmo, si deve presumere che un'implementazione che può sfruttare il parallelismo. Il documento n3408 discute opzioni per parallelizzazione e punti alla libreria Thrust, che è sia un utilizzabile reimplementazione in parallelo abilitato degli algoritmi standard e un concetto proof-of-per la futura standardizzazione di parallelismo negli algoritmi.

Considerando l'implementazione di Thrust di generate, chiama gen in un ciclo parallelo ogni volta che la categoria iteratore è ad accesso casuale. Come hai osservato, questo è coerente con lo standard, quindi non dovresti assumere che generate sarà sempre sequenziale. (Ad esempio, un thread-safe std::rand può essere utilizzato in modo efficiente con generate e non richiede chiamata sequenziale.)

Gli unici algoritmi che garantiscono invocazione sequenziale sono quelli in numeric; se il tuo codice dipende dal richiamo sequenziale, devi utilizzare iota al posto di generate. Adattare un generatore esistente:

template<typename F> struct iota_adapter { 
    F f; 
    operator typename std::result_of<F()>::type() { return f(); } 
    void operator++() {} 
}; 
template<typename F> iota_adapter<F> iota_adapt(F &&f) { return {f}; } 

Usa come:

#include <numeric> 
#include <iostream> 

int main() { 
    int v[5], i = 0; 
    std::iota(std::begin(v), std::end(v), iota_adapt([&i]() { return ++i; })); 
    for (auto i: v) std::cout << i << '\n'; 
} 
+1

Interessante! Stranamente, gli autori di spinte sembrano assumere sequenzialità per 'std :: generate': guardando gli esempi di codice su http: //thrust.github.it /, usano 'std :: generate' e il commento sopra dice" genera numeri casuali 32M in serie ". 'iota' è un sostituto insoddisfacente, richiede che il tipo restituito possa avere un' operatore ++ 'che faccia quello che vuoi. Penso che l'implementazione di una funzione 'seq_generate' potrebbe essere la soluzione migliore, dato che non è esattamente difficile da fare. – us2012

+0

L'ho letto come "in serie" anziché in parallelo (poiché 'std :: rand' potrebbe non essere a conoscenza del parallelismo o di sicurezza) piuttosto che implicare sequenzialità. Non è troppo difficile adattare 'iota'; Fornirò un esempio. – ecatmur

+0

Due piccoli commenti: 1) 5 algoritmi formano '': 'for_each',' copy', 'copy_backward',' move' e 'move_backward' sono anche sequenziali. 2) la dicitura per la garanzia di sequenzialità per 'iota' è quella dispari, dal momento che non usa la frase" in ordine "ma piuttosto" incrementa valore come se per valore ++ ". – TemplateRex