2

The print function in Python automatically separates its arguments with a customisable separator. Is there any way to emulate this behavior in C++ by using stream manipulators?

That is, the following C++ code:

std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;

Should work similar to the following Python code:

print(1, "two", 3, sep=", ")

The desired output would be:

1, two, 3

How would I go about implementing custom::sep? It seems a bit more tricky than your standard custom manipulator, because it cannot just change the next item on the stream, like here or here. It should be sticky until the next custom::sep or std::endl. Furthermore, it cannot just work on numbers or certain types, like here. It should work with any streamable type.

Community
  • 1
  • 1
kloffy
  • 2,928
  • 2
  • 25
  • 34
  • streams don't support your proposed design. there is no hook for "output of an item". but why not code up a `print` function instead of trying to cajole streams into doing unnatural things? or you can support `<<` notation for a string as destination, i.e. a string builder. both are reasonably easy to do. – Cheers and hth. - Alf Apr 03 '14 at 14:14
  • 2
    @kloffy It is possible , but not directly inside `std::cout`. I'm writing a reply – Raxvan Apr 03 '14 at 14:18
  • Thanks for the comments. Part of the motivation for my question was finding out whether it is possible. I am looking forward to seeing your solution @Raxvan! – kloffy Apr 03 '14 at 14:26

2 Answers2

3

The issue with the solution you posted is that it relies on customizing the way integers get formatted using facets. Unfortunately, I do not think there is a corresponding facility which would work for arbitrary types.

There is. You can use utilize the underlying buffer of the stream to get what you want. The buffer is where the character sequence is eventually gathered for maintenance. The following code makes a stream buffer that holds a reference to the object whose character sequence you wish to use. We set the std::ios_base::unitbuf format flag so that the stream is flushed on each output operation (so we can add the separator to the end).

By extension, it also allows you to uninstall the separator and makes sure that no memory is leaked in the process:

#include <iostream>
#include <string>

namespace custom
{
    struct sep_impl
    {
        sep_impl(std::string const& separator);
        std::string separator;
    };
    
    sep_impl sep(std::string const& str)
    {
        return sep_impl(str);
    }
    
    std::ostream& nosep(std::ostream& os);
}

int separatorEnabled()
                      { static int idx = std::ios_base::xalloc(); return idx; }
int getSeparator()    { static int idx = std::ios_base::xalloc(); return idx; }

struct custom_separator : std::streambuf
{
public:
    custom_separator(std::ostream& _stream) : stream(_stream)
    { }
    
    int_type overflow(int_type c)
    {
        return stream.rdbuf()->sputc(c);
    }
    
    int sync()
    {
        if (stream.iword(separatorEnabled()))
        {
            void*& p = stream.pword(getSeparator());
            stream << *static_cast<std::string*>(p);
            return 0;
        }
        return stream.rdbuf()->pubsync();
    }
private:
    std::ostream& stream;
};

void cleanup(std::ios_base::event evt, std::ios_base& str, int idx)
{
    if (str.iword(separatorEnabled()) && evt == std::ios_base::erase_event)
    {
        void*& p = str.pword(idx);
        delete static_cast<std::string*>(p);
        str.iword(separatorEnabled()) = false; 
    }
}

std::ostream& set_separator(std::ostream& os, const custom::sep_impl& manip)
{
    if (!os.bad())
    {
        os.pword(getSeparator()) = new std::string(manip.separator);
        os.register_callback(cleanup, getSeparator());
    }
    
    return os;
}

std::ostream& operator<<(std::ostream& os, const custom::sep_impl& manip)
{
    std::ostream* p = os.tie();
    if (p && !p->iword(separatorEnabled()))
    {
        set_separator(*p, manip);
        p->iword(separatorEnabled()) = true;
    }
    
    return os << std::unitbuf;
}

namespace custom
{
    sep_impl::sep_impl(std::string const& _sep) : separator(_sep) { }
    
    std::ostream& nosep(std::ostream& os)
    {
        cleanup(std::ios_base::erase_event, *os.tie(), getSeparator());
        os.tie(nullptr);
        return os << std::nounitbuf;
    }
    
    void install_separator(std::ostream& o1, std::ostream& o2)
    {
        static custom_separator csep(o2);
        o1.rdbuf(&csep);
        o1.tie(&o2);
    }
}

int main()
{
    std::ostream os(nullptr);
    custom::install_separator(os, std::cout);
    
    os << custom::sep(", ") << 4 << 2 << custom::nosep;
}

I'm sure there is also room for improvement, so if anyone has any suggestions they are very much appreciated.

Live Example

David G
  • 94,763
  • 41
  • 167
  • 253
  • This solution is closer to what I originally had in mind. Installing the separator still seems a bit cumbersome, but that could probably be streamlined. Thank you! – kloffy Apr 04 '14 at 02:12
  • @kloffy You can probably turn the installer into a manipulator so you can just do `os << custom::install_separator(std::cout)`. But in any case glad I could help! :) – David G Apr 04 '14 at 02:18
  • Yes, sounds good. I wish I could accept multiple answers, since both solutions are valid and have their own pros and cons. However, I slightly prefer this approach, so I am going to accept it. Thanks again @Raxvan and @0x499602D2! – kloffy Apr 05 '14 at 15:03
2

Ok , so this is defiantly not the cleanest/shortest solution but here is one way of doing it:

namespace custom
{
    struct sep
    {
        sep(const std::string & s)
            :separator(s)
        {
        }
        std::string separator;
    };
}
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
typedef CoutType& (*StandardEndLine)(CoutType&);
class SeparatorWrap
{
public:
    SeparatorWrap(std::ostream & _ofs, const custom::sep & s)
        : ofs(_ofs)
        , separator(s)
    {}
    template <class W>
    SeparatorWrap&  operator << (W && w)
    {
        ofs << separator.separator << w;
        return (*this);
    }
    ostream &  operator << (const StandardEndLine &)
    {
        //writing std::endl will remove the separator
        return ofs << std::endl;
    }
protected:
    std::ostream &          ofs;
    custom::sep         separator;
};
class SeparatorWrapFirst
{
public:
    SeparatorWrapFirst(std::ostream & _ofs, const custom::sep & s)
        : ofs(_ofs)
        , separator(s)
    {}
    template <class W>
    SeparatorWrap       operator << (W && w)
    {
        ofs << w;
        return SeparatorWrap(ofs, separator);
    }
    ostream &       operator << (const StandardEndLine &)
    {
        //writing std::endl will remove the separator
        return ofs << std::endl;
    }
protected:
    std::ostream &          ofs;
    custom::sep         separator;
};
SeparatorWrapFirst operator << (std::ostream & ofs,const custom::sep & s)
{
    return SeparatorWrapFirst(ofs, s);
}


int main()
{
    std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
}

Here is how it works:

std::cout << custom::sep(", ") returns a class of type SeparatorWrapFirst (using the global operator <<) which is used to write one value without separators the output. This is because if you have one element you don't need to write the separator.

After the first operator << from SeparatorWrapFirst is called, the class SeparatorWrap is returned and that prints with the separator also. This is for multiple values.

Edit:

So from the comments (@gexicide) it appears that i's possible to put custom manipulator inside std::cout. This could allow you to do something like:

std::cout << custom::sep(", ");
std::cout << 1 << "two" << 3 << std::endl;

Where the first solution from above will not work for this.

Community
  • 1
  • 1
Raxvan
  • 6,257
  • 2
  • 25
  • 46
  • Problem here might be that the next call to `std::cout <<` will have forgotten the seperator. If this behaviour is intentional, then this solution is perfectly fine. If you want sticky behaviour, check out this answer: http://stackoverflow.com/a/13335615/1408611 – gexicide Apr 03 '14 at 14:51
  • 1
    @gexicide yes, to solve this problem the only way i see now is to have a custom implementation for `std::cout` that has extra features inside. But for that solution is a lot of work. – Raxvan Apr 03 '14 at 14:54
  • It is possible without a custom cout implementation, check the answer in my link above. ios_base has a way store custom per-stream data. – gexicide Apr 03 '14 at 14:55
  • This is indeed a correct implementation of my specification! Only one minor bug: I believe the overload taking StandardEndLine should return the plain stream again. I will leave this question open for a bit, since there seem to be some alternative ideas floating around. However, thanks a lot for the great solution and explanation! – kloffy Apr 03 '14 at 15:15
  • @kloffy whell yes , it would make sens after you write `std::endl` the separator should be invalidated and in this case you need to return `ostream& ofs` in both `SeparatorWrapFirst` and `SeparatorWrap` – Raxvan Apr 03 '14 at 15:18
  • @gexicide The issue with the solution you posted is that it relies on customizing the way integers get formatted using facets. Unfortunately, I do not think there is a corresponding facility which would work for arbitrary types. – kloffy Apr 03 '14 at 15:25