2

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。此外,它不能只对数字或某些类型起作用,比如这里。它应该适用于任何可流式传输的类型。

4

2 回答 2

3

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

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

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

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

我相信还有改进的余地,所以如果有人有任何建议,他们将不胜感激。

Live Example

于 2014-04-03T22:31:38.827 回答
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(使用 global operator <<),用于在输出中写入一个不带分隔符的值。这是因为如果您有一个元素,则不需要编写分隔符。

在调用第一个运算符<<from后SeparatorWrapFirst,返回该类SeparatorWrap并使用分隔符打印。这适用于多个值。

编辑:

所以从评论(@gexicide)看来,我可以把自定义操纵器放在里面std::cout。这可以让您执行以下操作:

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

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

于 2014-04-03T14:42:29.700 回答