2010-03-04 21 views
10

Suppongo che questa potrebbe essere una domanda semplice per tutti i guru qui ma in qualche modo non sono riuscito a capire la risposta.Domanda di operatore di flusso C++

Voglio essere in grado di scrivere le cellule CSV per lo streaming semplice come questo:

stream << 1 << 2 << "Tom" << std::endl; 

che creerebbe output come 1,2, Tom. Come posso ottenerlo? Ho pensato che ho bisogno di creare streambuf personalizzato (poiché non penso che sia il modo giusto per farlo a livello di streaming, sarebbe un vero dolore solo per sovraccaricare < < per tutti i tipi), ma non sono sicuro di come < < è normalmente implementato. Chiama mettere o scrivere o cosa? Devo scavalcare quelli o cosa? O ho appena perso qualcosa completamente?

Apprezzerei tutto l'aiuto :)

Cheers,

risposta

10

Ottenere qualcosa come il 98% della strada là isn' t tremendamente difficile:

#include <iostream> 

class add_comma { 
    std::ostream &os; 
    bool begin; 
    typedef add_comma &ref; 
public: 
    add_comma(std::ostream &o) : os(o), begin(true) {} 

    template <class T> 
    ref operator<<(T const &t) { 
     if (!begin) 
      os << ","; 
     os << "\"" << t << "\""; 
     begin = false; 
     return *this; 
    } 

    ref operator<<(std::ostream &manip(std::ostream &o)) { 
     if (&manip == &std::endl) 
      reset(); 
     manip(os); 
     return *this; 
    } 

    void reset() { begin = true; } 

    operator void *() { return (void *)os; } 
}; 

int main() { 
    add_comma a(std::cout); 

    a << 1 << 2 << "This is a string" << std::endl; 
    a << 3 << 4 << "Another string" << std::endl; 
    return 0; 
} 

Modifica: ho risolto il problema almeno in qualche modo - ora mette solo com mas tra gli elementi che sono scritti, non all'inizio di una riga. Solo, tuttavia, riconosce "endl" come segnalazione all'inizio di un nuovo record - una nuova riga in una stringa letterale, ad esempio, non funzionerà.

+0

Sì, in effetti avevo già pensato a questa soluzione ma non avrebbe funzionato con i manipolatori (gli altri problemi con virgole ovunque e citando un'intera cella se conteneva delimitatore potevano essere facilmente risolti all'interno della funzione dell'operatore sovraccarico). Ora, ripensandoci, suppongo di poter scavalcare anche << per i manipolatori. Mi sto solo chiedendo se questo è il modo giusto per risolvere il problema :) – Tom

+0

Ho fatto un po 'più di lavoro su di esso. È ora al punto che probabilmente è almeno ragionevolmente utilizzabile. Non gestirà manipolatori che prendono parametri, ma ciò dovrebbe semplicemente essere una questione di aggiungere (ancora) un altro sovraccarico. Non dovrebbe essere troppo orribile, ma un dolore comunque. –

+0

Questa è una buona soluzione. Supponiamo che ci fosse qualche alternativa alla definizione di una classe wrapper (ad esempio, ereditando da 'std :: ostream' e sovrascrivendo qualcosa, o un magico manipolatore' csv_mode'). Se si desidera inserire tipi definiti dall'utente, si sarebbe nei guai. I metodi personalizzati dell'operatore << 'sono di solito implementati usando i metodi built-in' operator << ', così si finirebbe con delle virgole extra. – Dan

0

Se A è un iteratore sugli elementi ...

copy(A, A + N, ostream_iterator<int>(cout, ",")); 
+1

Hm, non so come sarebbe questo mi aiuterà? Non ho alcun contenitore per i valori scritti nello stream, sono scritti così come sono generati. Inoltre, l'iteratore è per tipo specifico, ma mi piacerebbe scrivere qualcosa nello stesso modo in cui lo avrei fatto con lo stream normale. – Tom

5

Mentre riesco ad apprezzare l'idea di sovraccaricare l'operatore del flusso, metterei in dubbio la pratica per il problema in questione.

1. Object-Oriented approccio

Se siete disposti a scrivere in un file .csv, quindi ogni linea dovrebbe probabilmente avere lo stesso formato di rispetto agli altri? Sfortunatamente il tuo operatore di flusso non lo controlla.

Penso che sia necessario creare un oggetto Line, che sarà filtrabile e convaliderà ogni campo prima di scriverli sul file (e scriverli con il formato corretto). Sebbene non siano di moda, avrai molte più possibilità di raggiungere un'implementazione solida qui.

Diciamo che (per esempio) che si desidera uscita 2 interi e una stringa:

class Line 
{ 
public: 
    Line(int foo, int bar, std::string firstName): 
    mFoo(foo), mBar(bar), mFirstName(firstName) 

    friend std::ostream& operator<<(std::ostream& out, const Line& line) 
    { 
    return out << line.mFoo << ',' << line.mBar << ',' 
       << line.mFirstName << std::endl; 
    } 
private: 
    int mFoo; 
    int mBar; 
    std::string mFirstName; 
}; 

E l'utilizzo rimane molto semplice:

std::cout << Line(1,3,"Tom") << Line(2,4,"John") << Line(3,5,"Edward"); 

2. wanna have fun?

Ora, questo può sembrare noioso, e si potrebbe desiderare di giocare, eppure hanno ancora un certo controllo su ciò che è scritto ... beh, mi permetta di introdurre programmazione meta modello nella mischia;)

Ecco l'uso previsto:

// Yeah, I could wrap this mpl_::vector bit... but it takes some work! 
typedef CsvWriter< mpl_::vector<int,int,std::string> > csv_type; 

csv_type(std::cout) << 1 << 3 << "Tom" << 2 << 4 << "John" << 3 << 5 << "Edward"; 

csv_type(std::cout) << 1 << 2 << 3; // Compile Time Error: 
            // 3 is not convertible to std::string 

Ora sarebbe interessante?Formattare la linea e garantire una misura di convalida ... Uno potrebbe sempre complicare la progettazione in modo che faccia di più (come la registrazione dei validatori per ogni campo, o per l'intera linea, ecc ...) ma è già abbastanza complicato.

// namespace mpl_ = boost::mpl 

/// Sequence: MPL sequence 
/// pos: mpl_::size_t<N>, position in the Sequence 

namespace result_of { 
    template <class Sequence, class pos> struct operator_in; 
} 

template < class Sequence, class pos = mpl_::size_t<0> > 
class CsvWriter 
{ 
public: 
    typedef typename mpl_::at<Sequence,pos>::type current_type; 
    typedef typename boost::call_traits<current_type>::param_type param_type; 

    CsvWriter(std::ostream& out): mOut(out) {} 

    typename result_of::operator_in<Sequence,pos>::type 
    operator<<(param_type item) 
    { 
    typedef typename result_of::operator_in<Sequence,pos>::type result_type; 

    if (pos::value != 0) mOut << ','; 
    mOut << item; 

    if (result_type::is_last_type::value) mOut << std::endl;    

    return result_type(mOut); 
    } 

private: 
    std::ostream& mOut; 
}; // class CsvWriter 


/// Lil' bit of black magic 
namespace result_of { // thanks Boost for the tip ;) 

    template <class Sequence, class pos> 
    struct operator_in 
    { 
    typedef typename boost::same_type< 
     typename mpl_::size<Sequence>::type, 
     typename mpl_::next<pos>::type 
     > is_last_type; 

    typedef typename mpl_::if_< 
     is_last_type, 
     CsvWriter< Sequence, mpl_::size_t<0> >, 
     CsvWriter< Sequence, typename mpl_::next<pos>::type > 
    >::type; 
    }; // struct operator_in<Sequence,pos> 

} // namespace result_of 

Ecco una scrittrice flusso che assicura che il file cvs è formattata correttamente ... scoprendo di ritorno a capo caratteri nelle stringhe;)

+0

Per 1) In realtà no :). Di solito ho bisogno di scrivere CSV in formato di terze parti e ogni riga può essere in formato diverso (direi che è una specie di protocollo, i csv vengono usati come "pacchetti" :)). Ma stavo parlando in generale - volevo solo gettare qualcosa su questo oggetto CSV e gestirebbe una corretta formattazione CSV ... 2) Wow, quel codice sembra davvero buono, ma devo cercare cosa fa effettivamente :)) – Tom