3
\$\begingroup\$

I was just playing around with some code and had the idea about writing some kind of ostream manipulator to delay the output of characters in a typewriter like style.

The focus was mostly on how to use it and was inspired by std::fixed or std::setprecision.

Overall it works pretty well. I just want to make sure that I didn't miss anything that may backfire any time.

The code is just for playing around and doesn't have a real use case (yet).

#include <iostream>
#include <string>
#include <utility>
#include <thread>

namespace manip
{
    struct delayed : std::ostream, std::streambuf
    {
        std::ostream* m_postream;
        std::chrono::milliseconds m_ms;

        delayed(std::chrono::milliseconds ms)
            : std::ostream(this)
            , m_postream(nullptr)
            , m_ms(ms)
        {}

        delayed(std::ostream& o, std::chrono::milliseconds ms)
            : std::ostream(this)
            , m_postream(&o)
            , m_ms(ms)
        {}

        int overflow(int c) override
        {
            std::this_thread::sleep_for(m_ms);
            if (m_postream)
            {
                (*m_postream) << (char)c;
            }
            return 0;
        }

        friend delayed& operator<<(std::ostream&, delayed&&);
    };

    delayed& operator<<(std::ostream& o, delayed&& d)
    {
        delayed* delayed_ostream = dynamic_cast<delayed*>(&o);
        if (delayed_ostream)
        {
            d.m_postream = delayed_ostream->m_postream;
        }
        else
        {
            d.m_postream = &o;
        }
        return d;
    }
}

int main()
{
    using namespace std::literals;
    using namespace manip;

    std::cout 
        << delayed(200ms) << "Wake up Neo."             << delayed(1100ms) << std::endl
        << delayed(200ms) << "The Matrix has you."      << delayed(3100ms) << std::endl
        << delayed(200ms) << "Follow the white Rabbit." << delayed(1100ms) << std::endl;
}

As it seems my intention on what delayed should do and how it works weren't very clear. So here is what the example will do as instructions:

//delayed(200ms) is passed to the stream: delay all following characters by 200ms
<wait 200ms><print 'W'>
<wait 200ms><print 'a'>
<wait 200ms><print 'k'>
<wait 200ms><print 'e'>
<wait 200ms><print ' '>
<wait 200ms><print 'u'>
<wait 200ms><print 'p'>
<wait 200ms><print ' '>
<wait 200ms><print 'N'>
<wait 200ms><print 'e'>
<wait 200ms><print 'o'>
<wait 200ms><print '.'>
//delayed(1100ms) is passed to the stream: delay all following characters by 1100ms
<wait 1100ms><print endl>
//delayed(200ms) is passed to the stream: delay all following characters by 200ms
<wait 200ms><print 'T'>
<wait 200ms><print 'h'>
<wait 200ms><print 'e'>
<wait 200ms><print ' '>
<wait 200ms><print 'M'>
<wait 200ms><print 'a'>
<wait 200ms><print 't'>
<wait 200ms><print 'r'>
<wait 200ms><print 'i'>
<wait 200ms><print 'x'>
<wait 200ms><print ' '>
<wait 200ms><print 'h'>
<wait 200ms><print 'a'>
<wait 200ms><print 's'>
<wait 200ms><print ' '>
<wait 200ms><print 'y'>
<wait 200ms><print 'o'>
<wait 200ms><print 'u'>
<wait 200ms><print '.'>
//delayed(3100ms) is passed to the stream: delay all following characters by 3100ms
<wait 3100ms><print endl>
//delayed(200ms) is passed to the stream: delay all following characters by 200ms
<wait 200ms><print 'F'>
<wait 200ms><print 'o'>
<wait 200ms><print 'l'>
<wait 200ms><print 'l'>
<wait 200ms><print 'o'>
<wait 200ms><print 'w'>
<wait 200ms><print ' '>
<wait 200ms><print 't'>
<wait 200ms><print 'h'>
<wait 200ms><print 'e'>
<wait 200ms><print ' '>
<wait 200ms><print 'w'>
<wait 200ms><print 'h'>
<wait 200ms><print 'i'>
<wait 200ms><print 't'>
<wait 200ms><print 'e'>
<wait 200ms><print ' '>
<wait 200ms><print 'R'>
<wait 200ms><print 'a'>
<wait 200ms><print 'b'>
<wait 200ms><print 'b'>
<wait 200ms><print 'i'>
<wait 200ms><print 't'>
<wait 200ms><print '.'>
//delayed(1100ms) is passed to the stream: delay all following characters by 1100ms
<wait 1100ms><print endl>
\$\endgroup\$
2
\$\begingroup\$

I found one improvement myself by altering the operator<< function into:

std::ostream& operator<<(std::ostream& o, delayed&& d)
{
    using namespace std::literals;
    delayed* delayed_ostream = dynamic_cast<delayed*>(&o);
    if (d.m_ms == 0ms)
    {
        if (delayed_ostream)
        {
            return *delayed_ostream->m_postream;
        }
        else
        {
            return o;
        }
    }
    if (delayed_ostream)
    {
        d.m_postream = delayed_ostream->m_postream;
    }
    else
    {
        d.m_postream = &o;
    }
    return d;
}

This will minimize the effect of delayed when it is used to disable the delay by using delayed(0ms).

Example:

std::cout << delayed(200ms) << "Slow..." << delayed(0ms) << "Fast..." << delayed(200ms) << "Slow..." << std::endl;

The part "Fast..." will be printed immediately without any indirection through the delay object.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.