2010-03-04 4 views
10

Je suppose que cela pourrait être une question simple pour tous les gourous ici, mais je ne pouvais pas en quelque sorte comprendre la réponse.C++ opérateur de flux question

Je veux être capable d'écrire des cellules csv pour diffuser aussi simple que cela:

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

qui créerait une sortie comme 1,2, Tom. Comment puis-je y parvenir? Je pensais que je devais créer un streambuf personnalisé (comme je ne pense pas que ce soit la bonne façon de le faire au niveau du flux, ce serait une vraie douleur juste pour surcharger < < pour tous les types) mais je ne sais pas comment < < est normalement implémenté. Est-ce qu'il appelle mettre ou écrire ou quoi. Devrais-je remplacer ceux ou quoi? Ou ai-je juste manqué quelque chose complètement?

Je vous remercie de toute aide :)

Cheers,

Répondre

10

Obtenir quelque chose comme 98% de la façon dont il n » t terriblement 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; 
} 

Éditer: J'ai fixé le code au moins à un certain degré - il met maintenant seulement com mas entre les éléments qui sont écrits, pas au début d'une ligne. Cependant, il reconnaît seulement "endl" comme signalant le début d'un nouvel enregistrement - une nouvelle ligne dans un littéral de chaîne, par exemple, ne fonctionnera pas.

+0

Oui, j'ai d'abord pensé à cette solution mais elle ne fonctionnerait pas avec les manipulateurs (les autres problèmes avec des virgules partout et citant la cellule entière si elle contient un délimiteur pourrait être facilement résolu dans la fonction opérateur surchargée). Maintenant, quand j'y repense, je suppose que je pourrais remplacer << pour les manipulateurs aussi. Je me demande simplement si c'est la bonne façon de résoudre le problème :) – Tom

+0

J'ai fait un peu plus de travail là-dessus. C'est maintenant au point que c'est probablement au moins raisonnablement utilisable. Il ne traitera pas les manipulateurs qui prennent des paramètres, mais cela devrait juste être une question d'ajout (encore) d'une surcharge. Ne devrait pas être trop horrible, mais une douleur de toute façon. –

+0

Ceci est une bonne solution. Supposons qu'il existe une alternative à la définition d'une classe wrapper (par exemple, héritage de 'std :: ostream' et de quelque chose de surchargé, ou d'un manipulateur magique' csv_mode'). Si vous vouliez insérer des types définis par l'utilisateur, vous seriez en difficulté. Les méthodes personnalisées 'operator <<' sont généralement implémentées à l'aide des méthodes intégrées 'operator <<', ce qui se traduit par des virgules supplémentaires. – Dan

0

Si A est un itérateur sur des éléments ...

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

Hm, pas sûr comment cela serait me aider? Je n'ai aucun conteneur pour les valeurs écrites dans le flux, elles sont écrites au fur et à mesure qu'elles sont générées. En plus, l'itérateur est pour un type spécifique mais j'aimerais écrire n'importe quoi de la même manière qu'avec un flux normal. – Tom

5

Bien que je puisse comprendre l'idée de surcharger l'opérateur de flux, je m'interrogerais sur la pratique du problème en question.

1. orienté objet approche

Si vous êtes prêt à écrire dans un fichier .csv, chaque ligne devrait probablement avoir le format même que les autres? Malheureusement, votre opérateur de flux ne le vérifie pas.

Je pense que vous devez créer un objet Line, qui sera streamable, et valider chaque champ avant de les écrire dans le fichier (et les écrire avec le format approprié). Bien que ce ne soit pas aussi à la mode, vous aurez beaucoup plus de chance d'obtenir une implémentation robuste ici.

Disons que (par exemple) que vous voulez à la sortie 2 entiers et une chaîne:

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; 
}; 

et son utilisation reste très simple:

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

2. Vous voulez vous amuser?

Maintenant, cela peut sembler terne, et vous pourriez vouloir jouer et encore ont encore un certain contrôle sur ce qui est écrit ... eh bien, permettez-moi de vous présenter la programmation méta modèle dans la mêlée;)

Voici l'utilisation prévue:

// 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 

Maintenant, ce serait intéressant non?Cela formaterait la ligne et assurerait une mesure de validation ... On pourrait toujours compliquer la conception pour qu'elle fasse plus (comme enregistrer des validateurs pour chaque champ, ou pour toute la ligne, etc ...) mais c'est déjà assez compliqué.

// 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 

Ici vous avez un écrivain de flux qui assure que le fichier cvs est correctement formaté ... mettant à nu les caractères de nouvelles lignes dans les cordes;)

+0

Pour 1) En fait non :). J'ai généralement besoin d'écrire CSV au format 3ème partie et chaque ligne peut être dans un format différent (je dirais que c'est un peu le protocole, les csv sont utilisés comme "paquets" :)). Mais je parlais en général - je voulais juste jeter quelque chose à cet objet CSV et il gérerait le format CSV ... 2) Wow, ce code a l'air vraiment bien, mais je dois regarder ce qu'il fait réellement :)) – Tom