如何实现自动添加分隔符的自定义粘性操纵器?

时间:2014-04-03 14:08:24

标签: c++ iostream manipulators

Python中的print函数会自动将其参数与可自定义的分隔符分开。有没有办法通过使用流操纵器在C ++中模拟这种行为?

即,以下C ++代码:

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

应该与以下Python代码类似:

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

所需的输出是:

1, two, 3

我将如何实施custom::sep?它似乎比标准自定义操纵器更棘手,因为它不能只改变流上的下一个项目,如herehere。它应该是粘性的,直到下一个custom::sepstd::endl。此外,它不仅可以处理数字或某些类型,例如here。它应该适用于任何可流式类型。

2 个答案:

答案 0 :(得分:3)

  

您发布的解决方案的问题在于它依赖于自定义整数使用构面进行格式化的方式。不幸的是,我认为没有相应的设施适用于任意类型。

有。您可以使用流的底层缓冲区来获得您想要的内容。缓冲区是最终收集字符序列以进行维护的地方。下面的代码生成一个流缓冲区,该缓冲区包含对您希望使用其字符序列的对象的引用。我们设置std::ios_base::unitbuf格式标志,以便在每次输出操作时刷新流(因此我们可以将分隔符添加到结尾)。

通过扩展,它还允许您卸载分隔符并确保在此过程中没有泄漏内存:

#include <iostream>

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

我确信还有改进的余地,所以如果有人有任何建议,我们非常感激。

Live Example

答案 1 :(得分:2)

好的,所以这绝对不是最干净/最短的解决方案,但这是一种方法:

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

以下是它的工作原理:

std::cout << custom::sep(", ")返回类型为SeparatorWrapFirst的类(使用全局operator <<),该类用于写入一个值而不用输出分隔符。这是因为如果你有一个元素,则不需要编写分隔符。

调用<<中的第一个运算符SeparatorWrapFirst后,将返回类SeparatorWrap,并使用分隔符进行打印。这适用于多个值。

编辑:

所以从评论(@gexicide)看来我可以放custom manipulator inside std::cout。这可以让你做类似的事情:

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

上面的第一个解决方案不适用于此。