27 votes

Pourquoi `<< std::endl` n'appelle pas l'opérateur que je veux qu'il appelle ?

Je cherchais une solution pour écrire dans un fichier et dans la console en même temps. J'ai trouvé une solution intéressante aquí .

Comme je travaille en pré C++11, j'ai dû faire un petit changement au code de Lightness Races in Orbit :

#include <iostream>
#include <fstream>
#include <string>

struct OutputAndConsole : std::ofstream
{
    OutputAndConsole(const std::string& fileName)
       : std::ofstream(fileName.c_str())   // constructor taking a string is C++11
       , fileName(fileName)
    {};

    const std::string fileName;
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
{
    std::cout << var;    
    static_cast<std::ofstream&>(strm) << var;
    return strm;
};

Il fonctionne bien à l'exception d'une petite chose qui me laisse perplexe. Si je l'utilise comme ça :

int main(){
    OutputAndConsole oac("testLog.dat");
    double x = 5.0;
    oac << std::endl;
    static_cast<OutputAndConsole&>(oac << "foo \n" << x << "foo").operator<<(std::endl);  
    oac << "foo" << std::endl;
}

alors tous les std::endl sont ignorés pour la sortie sur la console alors qu'ils apparaissent correctement dans le fichier. Je pense que lorsque j'utilise std::endl le site ostream::operator<< est appelé, ce qui permet d'imprimer dans le fichier mais pas dans la console. La ligne avec le static_cast<OutputAndConsole&> est ma tentative dilettante d'appeler l'opérateur correct, mais toujours seulement le saut de ligne de \n apparaît sur la console.

Pourquoi pour std::endl le mauvais opérateur est appelé ?

Comment puis-je appeler la bonne personne ?

PS : Je sais que je peux utiliser \n sans problème, mais j'aimerais quand même savoir ce qui se passe ici et comment le réparer.

12voto

Yakk Points 31636
struct OutputAndConsole : std::ofstream
{
  // ...
};

template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

Comme d'autres l'ont mentionné, std::endl est une fonction modèle. Une fonction modèle n'est pas une valeur c'est juste un nom.

Une fonction modèle peut être convertie en valeur si vous essayez de la passer à une fonction attendant une fonction d'une signature compatible. Il est no converti en une valeur s'il est passé à une fonction de modèle prenant T o const T& parce que le nom d'une fonction modèle représente un ensemble hôte de valeurs possibles.

Parce que std::endl n'est pas un argument valable pour votre écriture personnalisée operator<< il cherche ailleurs. Il trouve le std::ofstream 's operator<< qui prend une fonction de manipulateur io par pointeur de fonction explicite.

Celui-là marche, il peut convertir endl à ce type de pointeur de fonction ! Donc, joyeusement, il l'appelle.

Pour résoudre ce problème, ajoutez un operator<< surcharge pour OutputAndConsole qui prend les pointeurs des fonctions du manipulateur io.

La façon la plus simple de le faire est d'écrire une fonction d'aide :

template <class T>
void output_to(OutputAndConsole& strm, const T& var)
{
  std::cout << var;    
  static_cast<std::ofstream&>(strm) << var;
};

puis deux << les surcharges :

template<class T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) {
  output_to(strm, var);
  return strm;
}
OutputAndConsole& operator<<(OutputAndConsole& strm, std::ostream& (*var)(std::ostream&)) {
  output_to(strm, var);
  return strm;
}

ce qui provoque le std::endl pour trouver un modèle correspondant << .

11voto

user31264 Points 4083

Essayons quelque chose de plus simple :

#include <iostream>

struct Foo { };

template <typename T>
Foo& operator<<(Foo& foo, const T& var)
{
    std::cout << var;
    return foo;
};

int main(){
    Foo foo;
    foo << std::endl;
}

Cela ne se compile pas :

a1.cpp: In function ‘int main()’:
a1.cpp:14:9: error: no match for ‘operator<<’ (operand types are ‘Foo’ and ‘<unresolved overloaded function type>’)
     foo << std::endl;
         ^
a1.cpp:14:9: note: candidates are:
a1.cpp:6:6: note: template<class T> Foo& operator<<(Foo&, const T&)
 Foo& operator<<(Foo& foo, const T& var)
      ^
a1.cpp:6:6: note:   template argument deduction/substitution failed:
a1.cpp:14:17: note:   couldn't deduce template parameter ‘T’

Pourquoi ? Que tente de nous dire le compilateur ?

La définition de std::endl se trouve ici : http://en.cppreference.com/w/cpp/io/manip/endl

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

Alors std::endl n'est pas une variable. Il s'agit d'une modèle . Plus précisément, une fonction template. Dans mon petit code, le compilateur est incapable d'instancier le template.

Lorsque nous appelons directement std::cout << std::endl; le compilateur instancie std::endl de CharT y Traits de decltype(std::cout) .

Dans votre code, le compilateur instancie plutôt le modèle en utilisant CharT y Traits de std::ofstream parce que votre OutputAndConsole est un descendant de std::ofstream . Quand std::cout essaie d'afficher la mauvaise instanciation de std::endl il échoue.

PS : Le dernier paragraphe n'est que partiellement correct. Lorsque vous écrivez

oac << something;

something a le type T,

Il peut, théoriquement, appeler l'une ou l'autre des deux situations suivantes

std::ofstream& std::ofstream::operator<<(T)  // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);

La première définition est possible parce que OutputAndConsole a hérité de la fonction membre operator<< de std::ofstream . Le deuxième formulaire est fourni par vous.

Lorsque something est une variable, elle utilise la deuxième définition.

Lorsque something est un modèle, il ne peut pas utiliser la deuxième définition, car il n'y a aucun moyen de déterminer les paramètres du modèle. Il utilise donc la première définition. Par conséquent,

oac << std::endl;  // std::endl is a template

est équivalent à

static_cast<ofstream&>(oac) << std::endl;

Nous pouvons le voir par le code suivant :

#include <iostream>

struct Foo : std::ofstream {};

template <typename T>
Foo& operator<<(Foo& strm, const T& var)
{
    std::cout << "X" << std::endl;
    return strm;
};

int main() {
    Foo oac;
    oac << std::endl;
}

Ce code n'imprime PAS "X".

5voto

Mekap Points 1927

std::endl est une fonction, pas une chaîne de caractères. Votre méthode surchargée prend une chaîne de caractères pour la surcharge, donc ce n'est pas celle-ci qui est appelée lorsque vous faites << std::endl

Vous devez créer un opérateur qui prend une fonction qui a la même signature que std:endl pour faire votre surcharge.

 std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )

4voto

Dieter Lücking Points 10938

Je suggère de ne pas implémenter la fonctionnalité standard de flux d'E/S via l'interface stream, mais l'interface streambuffer. Une interface de flux personnalisée conduit généralement à des problèmes dès que le flux est reconnu comme un std::istream/std::ostream, seulement (à cause d'un opérateur, manipulateur ou fonction prenant une référence à un flux).

Vous pouvez utiliser :

#include <array>
#include <iostream>
#include <sstream>
#include <vector>

// BasicMultiStreamBuffer
// ============================================================================

/// A (string) stream buffer for synchronizing writes into multiple attached buffers.
template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStreamBuffer : public std::basic_stringbuf<Char, Traits, Allocator>
{
    // Types
    // =====

    private:
    typedef typename std::basic_stringbuf<Char, Traits> Base;

    public:
    typedef typename std::basic_streambuf<Char, Traits> buffer_type;
    typedef typename buffer_type::char_type char_type;
    typedef typename buffer_type::traits_type traits_type;
    typedef typename buffer_type::int_type int_type;
    typedef typename buffer_type::pos_type pos_type;
    typedef typename buffer_type::off_type off_type;

    private:
    typedef typename std::vector<buffer_type*> container_type;

    public:
    typedef typename container_type::size_type size_type;
    typedef typename container_type::value_type value_type;
    typedef typename container_type::reference reference;
    typedef typename container_type::const_reference const_reference;
    typedef typename container_type::iterator iterator;
    typedef typename container_type::const_iterator const_iterator;

    // Construction/Destructiion
    // =========================

    public:
    BasicMultiStreamBuffer()
    {}

    template <typename...Buffers>
    BasicMultiStreamBuffer(Buffers* ...buffers) {
        std::array<buffer_type*, sizeof...(Buffers)> buffer_array{buffers...};
        m_buffers.reserve(buffer_array.size());
        for(auto b : buffer_array) {
            if(b)
                m_buffers.push_back(b);
        }
    }

    template <typename Iterator>
    BasicMultiStreamBuffer(Iterator first, Iterator last)
    :   m_buffers(first, last)
    {}

    ~BasicMultiStreamBuffer() {
        sync();
    }

    private:
    BasicMultiStreamBuffer(BasicMultiStreamBuffer const&); // No Copy.
    BasicMultiStreamBuffer& operator=(BasicMultiStreamBuffer const&); // No Copy.

    // Capacity
    // ========

    public:
    bool empty() const { return m_buffers.empty(); }
    size_type size() const { return m_buffers.size(); }

    // Iterator
    // ========

    public:
    iterator begin() { return m_buffers.begin(); }
    const_iterator begin() const { return m_buffers.end(); }
    iterator end() { return m_buffers.end(); }
    const_iterator end() const { return m_buffers.end(); }

    // Modifiers
    // =========

    public:
    /// Attach a buffer.
    void insert(buffer_type* buffer) {
        if(buffer) m_buffers.push_back(buffer);
    }

    /// Synchronize and detach a buffer.
    void erase(buffer_type* buffer) {
        iterator pos = this->begin();
        for( ; pos != this->end(); ++pos) {
            if(*pos == buffer) {
                char_type* p = this->pbase();
                std::streamsize n = this->pptr() - p;
                if(n)
                    sync_buffer(*pos, p, n);
                m_buffers.erase(pos);
                break;
            }
        }
    }

    // Synchronization
    // ===============

    private:
    int sync_buffer(buffer_type* buffer, char_type* p, std::streamsize n) {
        int result = 0;
        std::streamoff offset = 0;
        while(offset < n) {
            int k = buffer->sputn(p + offset, n - offset);
            if(0 <= k) offset += k;
            else {
                result = -1;
                break;
            }
            if(buffer->pubsync() == -1)
                result = -1;
        }
        return result;
    }

    protected:
    /// Synchronize with the attached buffers.
    /// \ATTENTION If an attached buffer fails to synchronize, it gets detached.
    virtual int sync() override {
        int result = 0;
        if( ! m_buffers.empty()) {
            char_type* p = this->pbase();
            std::streamsize n = this->pptr() - p;
            if(n) {
                iterator pos = m_buffers.begin();
                while(pos != m_buffers.end()) {
                    if(0 <= sync_buffer(*pos, p, n)) ++pos;
                    else {
                        pos = m_buffers.erase(pos);
                        result = -1;
                    }
                }
            }
        }
        this->setp(this->pbase(), this->epptr());
        if(Base::sync() == -1)
            result = -1;
        return result;
    }

    private:
    container_type m_buffers;
};

typedef BasicMultiStreamBuffer<char> OStreamBuffers;

// BasicMultiStream
// ============================================================================

template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> >
class BasicMultiStream : public std::basic_ostream<Char, Traits>
{
    // Types
    // =====

    private:
    typedef std::basic_ostream<Char, Traits> Base;

    public:
    typedef BasicMultiStreamBuffer<Char, Traits, Allocator> multi_buffer;
    typedef std::basic_ostream<Char, Traits> stream_type;

    typedef typename multi_buffer::buffer_type buffer_type;
    typedef typename multi_buffer::char_type char_type;
    typedef typename multi_buffer::traits_type traits_type;
    typedef typename multi_buffer::int_type int_type;
    typedef typename multi_buffer::pos_type pos_type;
    typedef typename multi_buffer::off_type off_type;

    typedef typename multi_buffer::size_type size_type;
    typedef typename multi_buffer::value_type value_type;
    typedef typename multi_buffer::reference reference;
    typedef typename multi_buffer::const_reference const_reference;
    typedef typename multi_buffer::iterator iterator;
    typedef typename multi_buffer::const_iterator const_iterator;

    // Construction
    // ============

    public:
    BasicMultiStream()
    :   Base(&m_buffer)
    {}

    template <typename ...Streams>
    BasicMultiStream(Streams& ...streams)
    :   Base(&m_buffer), m_buffer(streams.rdbuf()...)
    {}

    private:
    BasicMultiStream(const BasicMultiStream&); // No copy.
    const BasicMultiStream& operator = (const BasicMultiStream&); // No copy.

    // Capacity
    // ========

    public:
    bool empty() const { return m_buffer.empty(); }
    size_type size() const { return m_buffer.size(); }

    // Iterator
    // ========

    public:
    iterator begin() { return m_buffer.begin(); }
    const_iterator begin() const { return m_buffer.end(); }
    iterator end() { return m_buffer.end(); }
    const_iterator end() const { return m_buffer.end(); }

    // Modifiers
    // =========

    public:
    template <typename StreamIterator>
    void insert(StreamIterator& first, StreamIterator& last)
    {
        while(first != last)
            insert(*first++);
    }
    void insert(stream_type& stream) { m_buffer.insert(stream.rdbuf()); }
    void erase(stream_type& stream) { m_buffer.erase(stream.rdbuf()); }

    private:
    multi_buffer m_buffer;
};

typedef BasicMultiStream<char> MultiStream;

int main() {
    MultiStream s(std::cout, std::cerr, std::clog);
    s << "Hello World" << std::endl;
    printf("[Three lines of output]\n");
}

Notez que la seule fonction qui applique des changements à l'interface std: : basic_streambuf est virtual int sync() override .

Les classes de flux de base standard ne fournissent aucune interface, à part la dérivation et l'initialisation d'une classe de flux personnalisée. L'interface réelle (virtuelle) est le tampon de flux standard basic_streambuf .

3voto

user31264 Points 4083

Pour que cela fonctionne, je créerais mon propre ensemble de manipulateurs :

struct ManipEndl {};
const ManipEndl manip_endl;
OutputAndConsole& operator<<(OutputAndConsole& strm, const ManipEndl& foo)
{
    std::cout << std::endl;    
    static_cast<std::ofstream&>(strm) << '\n'; // no need for std::endl here
    return strm;
};

int main(){
    OutputAndConsole oac("testLog.dat");
    double x = 5.0;
    oac << manip_endl;
    oac << x << manip_endl << "foo" << manip_endl;  
    oac << "foo" << manip_endl;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X