Esiste un buon modo per non scrivere a mano tutte e dodici le funzioni del contenitore richieste per un tipo personalizzato in C ++?

2

In StackOverflow ho chiesto, cosa è il modo migliore per esporre l'iterazione di stile STL personalizzata . La risposta sembra essere quella di implementare dodici funzioni: sei membri, sei non membri (forse usando alcune macro per aiutare).

In C #, posso fare lo stesso con una sola funzione usando yield return ; Il C ++ è grandioso, ma cose come questa possono dargli una reputazione davvero pessima. C'è un buon modo (cioè, senza macro?) Per non dover scrivere queste dodici funzioni?

    
posta Ðаn 31.08.2016 - 15:51
fonte

3 risposte

3

Le funzioni globali std::begin e std::end (insieme alle loro controparti c in C ++ 14) troveranno le funzioni membro nominate dei contenitori. Quindi non è necessario fornire versioni non associate di queste funzioni.

Gli intervalli non membri sono importanti solo se qualcuno desidera effettuare una chiamata non qualificata a begin / end . E il concetto di contenitore in C ++ non lo presenta come un'interfaccia valida per i contenitori (non che ciò abbia importanza per te, dal momento che poche API C ++ prendono i contenitori per cominciare). for basato sull'intervallo cercherà membro begin/end prima dei non membri, quindi anche tu sei al sicuro.

Ma la funzione membro che dovrai scrivere tu stesso. Puoi evitare di scrivere le versioni cbegin/cend scrivendo const versioni di begin/end e utilizzando il CRTP per generare gli altri:

template<typename Derived>
class cbegin_cend
{
public:
    auto cbegin() const {return This()->begin();}
    auto cend() const {return This()->end();}
private:
    auto This() const {return static_cast<const Derived*>(this);}
};

Per usarlo, dovresti fare questo:

class mine : public cbegin_cend<mine>
{
public:
    const_iterator begin() const;
    const_iterator end() const;
};

Ciò richiede la funzionalità di deduzione del tipo di ritorno automatico del C ++ 14. È necessario perché altrimenti dovresti digitare il nome del tipo restituito in cbegin e cend . E la classe base CRTP non può rilevarlo senza aiuto esterno. Potremmo usare una classe di caratteri esterni per fornirla, ma questo è un lavoro extra che il contenitore deve fare.

    
risposta data 31.08.2016 - 18:29
fonte
5

On stackoverflow I asked, what is the preferred way to expose custom STL-style iteration?

... In C#, I can do about the same with just a single function using yield return

No, davvero non puoi.

Il yield return di C # è simile a un generatore Python, che in termini C ++ è un InputIterator , la più semplice delle sei categorie di iteratori descritte qui .

La tua domanda originale implementa un RandomAccessIterator , che è significativamente più potente di questo - per esempio, tu può eseguire la ricerca binaria diretta ad accesso casuale sugli iteratori restituiti in quel codice o ordinare i contenuti di un contenitore non const sul posto.

Se tutto ciò che vuoi è un iteratore di input in stile C # / Python, puoi probabilmente semplificare la produzione con CRTP come suggerito da Cort Ammon nei commenti, e puoi sicuramente perdere i sovraccarichi non costanti.

Un riepilogo dei commenti successivi:

Q: In PogoStickLang, I can create a new pogo stick by just typing !. In RocketLang I need to type loads more stuff to make a pogo stick, and it seems over-complex

A: In RocketLang you created a rocket, which is why it's more complex than a pogo stick. Either

  • create a pogo stick if that's all you want. It's more work when not using a language tightly focused on the unique needs of pogo stick-users, but still less work than creating a rocket
  • or, create a rocket and actually take advantage of all the things it can do that a pogo stick can't, like flying to the moon or incinerating your enemies.

No, non è del tutto serio. No, non sto suggerendo che C # sia un linguaggio giocattolo, e no, non sto suggerendo che questo sia un confronto proporzionato dei poteri relativi di Input- e RandomAccessIterators. Non ho nulla contro i pogo stick e raramente, se non mai, incenerisco i miei nemici .

    
risposta data 31.08.2016 - 17:05
fonte
1

No.

Come afferma la domanda collegata, la caratteristica Container in C ++ indica che sono necessarie dodici funzioni per l'iterazione in varie circostanze: const v. non-const, membro v. non-membro, ecc.

Potresti essere in grado di utilizzare una macro per evitare parte della piastra, ma ora hai meno visibilità sul tuo codice. Cosa succede se c'è un bug nel codice generato? Puoi guardare il tuo file sorgente C ++ e vederlo? No: è necessaria la fase aggiuntiva di controllo dell'output del preprocessore. C'è un motivo per cui le macro sono generalmente evitate in un buon codice C ++ al di fuori delle protezioni di inclusione (sì #pragma once esiste ma le guardie sono ancora comuni). Rendono il codice meno leggibile e più difficile da mantenere. Questo è parte del motivo per cui C ++ ci fornisce strumenti come modelli e tratti, per affrontare la causa principale di alcuni dei motivi per l'utilizzo delle macro.

Le funzioni per acquisire gli iteratori sono generalmente brevi e semplici. Scrivi a mano le funzioni e fallo con esso.

    
risposta data 31.08.2016 - 16:19
fonte

Leggi altre domande sui tag