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

How to implement custom sticky manipulator that automatically adds separators?

本文关键字:自定义 分隔符 操纵 添加 何实现 实现      更新时间:2023-10-16

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

也就是说,以下C++代码:

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

工作原理应类似于以下 Python 代码:

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

所需的输出将是:

1, two, 3

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

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

有。您可以使用利用流的基础缓冲区来获取所需的内容。缓冲区是最终收集字符序列以进行维护的位置。下面的代码创建一个流缓冲区,该缓冲区保存对要使用的字符序列的对象的引用。我们设置 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;
}

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

现场示例

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

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)来看,我可以将自定义操纵器放入std::cout.这可以让你执行以下操作:

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

上面的第一个解决方案对此不起作用。