2015-08-19 5 views
47

Quando si utilizzano le funzioni di STL come sort() o min_element() devo sempre specificare l'intervallo da iniziano e finiscono in modo esplicito:Perché devo sempre specificare l'intervallo nelle funzioni dell'algoritmo di STL in modo esplicito, anche se voglio lavorare sull'intero contenitore?

void range_example() 
{ 
    std::vector<int> list = {7, 3, 9, 1, 5, 2}; 
    auto found_element = std::min_element(list.begin(), list.end()); 
    std::cout << *found_element << std::endl; 
} 

questo ha un senso se ho intenzione di lavorare solo su parte del mio contenitore, ma più spesso ho bisogno le funzioni per lavorare sull'intero contenitore. C'è un motivo per cui non v'è una funzione di overload che consente per questo:

std::vector<int> list = {7, 3, 9, 1, 5, 2}; 
auto found_element = std::min_element(list); 

Esiste un modo per realizzare una chiamata di funzione per la gamma totale di un contenitore che ho trascurato?

MODIFICA: Sono consapevole che posso incapsulare quello in una funzione da solo, ma poiché questo deve essere fatto per tutte le funzioni, vorrei evitare che se c'è un modo migliore.

+9

Sede [Gamma v3] (https://github.com/ericniebler/range-v3). – ildjarn

+0

È possibile scrivere una funzione per questo. – songyuanyao

+5

Gli intervalli sono ora una bozza [Specifiche tecniche] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4382.pdf) quindi con un po 'di fortuna vedrai il supporto per loro spuntando in compilatori a un certo punto. – user657267

risposta

43

La maggior parte delle volte, la libreria standard è progettata per fornire l'interfaccia minima necessaria per eseguire tutte le attività richieste, ad es. & Thinsp; tenta di evitare l'aumento di interfaccia. È possibile operare su un intero contenitore quando l'algoritmo accetta una coppia di iteratori, ma non è possibile operare su un subrange se l'algoritmo accetta un contenitore. Quindi la coppia iteratore è più fondamentale, e quindi è quello che fornisce la libreria standard. Le funzioni di convenienza non sono solitamente incluse.

Tuttavia, non sei certo il primo a pensare in questo modo, e c'è l'intera libreria Boost.Range dedicata al trattamento di un intervallo (sia un contenitore che un intervallo arbitrario) come entità singola anziché una coppia di iteratori.

C'è anche una proposta formale per incorporare Eric Niebler's range library in una versione futura della libreria standard C++.

+1

Come commento laterale, in alcune circostanze l'uso dell'interfaccia contenitori, anziché degli iteratori, è molto più efficiente. Pensi di trovare elementi e così via. Il che è una buona ragione per fornire sia una versione iteratrice sia una versione basata su container di ciascun algoritmo, arriviamo a pensarci, quest'ultima invocando la versione basata su iteratore o quella specializzata fornita dal contenitore. – Deduplicator

+0

@Deduplicator Ho la sensazione che il fatto che il * container * stesso aiuti effettivamente l'efficienza, i contenitori standard tendono a fornire la funzione membro appropriata ('std :: list :: sort'). Dove è solo la categoria iteratore che aiuta, le implementazioni speciali per le categorie migliori di iteratore sono spesso utilizzate da std. biblioteca. – Angew

+1

Non aiuta molto nel codice generico se il contenitore fornisce un'opzione migliore, ma non viene scelto perché non abbiamo ancora replicato la logica di selezione ... – Deduplicator

7

Questo perché gli algoritmi STL sono indipendenti dal contenitore. Gli iteratori forniscono un modo uniforme per loro di lavorare, con l'unica limitazione di essere quali sono le garanzie che questo algoritmo richiede da questi iteratori.

Ad esempio, se si desidera eseguire una ricerca lineare per min_element(), sono necessari solo iteratori di inoltro (vale a dire devono supportare solo operator++). Quindi, puoi scrivere una semplice implementazione basata su modelli che funzionerà essenzialmente con ogni contenitore, nonostante il modo in cui il contenitore viene implementato sotto il cofano.

È possibile sovraccaricare le funzioni per prendere solo il contenitore e applicare begin() e end() su di esse, ma ciò significherebbe che si ha ancora un'interfaccia da ricordare.

Modifica

Suppongo che ci sono un paio di altri argomenti che potrebbero essere fatte. Dal momento che STL era incentrato sulla bellezza matematica e sull'enfasi che gli algoritmi sono separati dai contenitori, il passaggio sempre da iteratori rafforzerebbe questa nozione.

D'altra parte, in termini di linguaggio C++ nel suo complesso, uno degli obiettivi principali di Stroustrup era quello di educare gli sviluppatori. La piena potenza degli algoritmi STL deriva dalla possibilità di passare intervalli di iteratore arbitrari, ma il più delle volte si desidera operare sull'intero contenitore. Se fornivi sovraccarichi per l'intero contenitore, si potrebbe sostenere che un gran numero di persone non si preoccuperebbe mai di imparare a utilizzare le versioni di intervallo, perché sarebbero proprio quelle versioni che rientrerebbero nella categoria "un'altra interfaccia da ricordare".

+1

Non è "tipo di iteratori". Puoi chiamarlo "concetto", "tipo requisiti" o "categoria iteratore". –

+0

Sì, ho cambiato il tipo di garanzie –

+1

Onestamente, non mi dispiacerebbe l'onere di ricordare un "passaggio del contenitore su cui vuoi lavorare" davvero "extra" che risparmierebbe digitare ".begin()" e '.end()' nel ~ 98% dei casi che ho mai chiamato 'std :: sort' o' std :: min_element' o quasi qualsiasi altra '' funzione, ma forse sono solo io. OTOH non è davvero una novità che la libreria standard cerca di ottimizzare per il caso più generale (anche quando ciò significa gettare l'usabilità nei casi più comuni prosciugando o non fornendo funzionalità ampiamente richieste - vedi la recente discussione sulla fornitura di 'split' per le stringhe). –

4

è possibile implementare il proprio:

template<class Container> 
typename Container::iterator min_element(Container& c) { 
    using std::begin; 
    using std::end; 
    return std::min_element(begin(c), end(c)); 
} 

std::vector<int> list = {7, 3, 9, 1, 5, 2}; 
auto found_element = min_element(list); 

Per completezza:

template<class Container> 
typename std::conditional< 
    std::is_const<Container>::value, 
    typename Container::const_iterator, 
    typename Container::iterator>::type min_element(Container& c) { 
    using std::begin; 
    using std::end; 
    return std::min_element(begin(c), end(c)); 
} 

e per sostenere gli array:

template<typename T, size_t N> 
T* min_element(T (&arr)[N]) { return std::min_element(arr, arr + N); } 
-1

È facile definire tale funzione da soli. Ad esempio

#include <iostream> 
#include <vector> 
#include <algorithm> 
#include <iterator> 

template <class T> 
decltype(auto) min_element(T &c) 
{ 
    return std::min_element(std::begin(c), std::end(c)); 
} 

int main() 
{ 
    int a[] = { 5, 7, 3, 1, 9, 6 }; 

    std::cout << *min_element(a) << std::endl; 

    std::vector<int> v(std::begin(a), std::end(a)); 

    std::cout << *min_element(v) << std::endl; 
}  

L'output del programma è

1 
1 

Ho fatto un tale suggerimento per algoritmi std::sort e std::reverse. Puoi leggere su di esso nel mio forum personale che supporto come la mia pagina internet perosale. here

Sebbene sia scritto in russo, è possibile tradurlo ad esempio con Bing o google.

+0

I commenti non sono per discussioni estese; questa conversazione è stata [spostata in chat] (http://chat.stackoverflow.com/rooms/87376/discussion-on-answer-by-vlad-from-moscow-why-do-i-have-to-always- specificare-the-ran). – Matt

6

Il motivo pratico per cui i sovraccarichi di container o di intervallo non sono stati ancora eseguiti riguarda la proposta di concetti.

In questo momento, gli algoritmi accettano numerosi parametri del modello e inseriscono i relativi requisiti. Se si passano tipi che non corrispondono ai requisiti, possono non riuscire a compilare o semplicemente non funzionare correttamente.

I sovraccarichi implicano quasi sempre semplicemente un numero diverso di parametri.

Se dovessimo aggiungere sovraccarichi di container/intervallo, dovremmo assegnare loro nuovi nomi (ick) oppure modificare gli algoritmi esistenti per renderli più intelligenti. Un sovraccarico (iteratore, iteratore, valore) e un sovraccarico (intervallo, valore, funzione) hanno lo stesso numero di argomenti e quello che viene chiamato potrebbe facilmente confondere il compilatore (e potrebbero verificarsi risultati imprevisti).

Mentre potremmo specificare i vincoli di sovraccarico su tutti gli algoritmi esistenti uno alla volta, quindi aggiungere gli overload per gli intervalli, a questo punto il codice e i requisiti sarebbero brutti. Dopo che i concetti sono stati aggiunti al linguaggio, entrambi speriamo di avere una serie di concetti concisi che descrivono quali dovrebbero essere i parametri e una funzionalità linguistica che rende l'implementazione facile e pulita.

Potrebbe risultare che questi algoritmi potrebbero non essere praticamente sovraccarichi degli algoritmi esistenti, a causa di motivi di compatibilità o di ciò che hai, ma anche questo sarà più facile da elaborare.

In origine, gli iteratori erano sufficienti e disaccoppiano i contenitori dagli algoritmi. Gli intervalli avrebbero potuto essere aggiunti in quel momento, ma il meccanismo linguistico per l'interpretazione dell'intervallo pulito dei contenitori era in qualche modo carente (decltype, ad esempio, è utile), e non era strettamente necessario. Da allora, il supporto di gamma è stato desiderato, ma non è facile farlo in modo pulito, e c'è (all'orizzonte) un'estensione di linguaggio che renderà molto più pulito e facile.

0

Questo è uno dei momenti in cui ritengo sia corretto utilizzare macro. Assicurati che il calcolo dell'espressione all'interno della macro non abbia effetti collaterali.

#include <boost/preprocessor/punctuation/comma.hpp> 
// this is just: #define BOOST_PP_COMMA() , 

#define RANGE_ARGS(container) container.begin () BOOST_PP_COMMA() container.end () 
#define RANGE_ARGS_C(container) container.cbegin () BOOST_PP_COMMA() container.cend () 
#define RANGE_ARGS_R(container) container.rbegin () BOOST_PP_COMMA() container.rend () 
#define RANGE_ARGS_CR(container) container.crbegin () BOOST_PP_COMMA() container.crend () 

Questo produce, nel tuo caso:

std::vector<int> list = {7, 3, 9, 1, 5, 2}; 
auto const found_element = std::min_element(RANGE_ARGS_C(list));