如何实现自动添加分隔符的自定义粘性操纵器
How to implement custom sticky manipulator that automatically adds separators?
Python 中的打印函数会自动使用可自定义的分隔符分隔其参数。有没有办法通过使用流操纵器在C++中模拟这种行为?
也就是说,以下C++代码:
std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
工作原理应类似于以下 Python 代码:
print(1, "two", 3, sep=", ")
所需的输出将是:
1, two, 3
我将如何实施custom::sep
?它似乎比标准自定义操纵器更棘手,因为它不能只是更改流中的下一个项目,例如此处或此处。它应该是粘的,直到下一个custom::sep
或std::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;
上面的第一个解决方案对此不起作用。
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 如何将点击的信号和插槽添加到qt中的自定义按钮中
- C++自定义比较函数
- 如何比较自定义类的std::变体
- std::设置自定义比较器
- 如何正确实现和访问运算符的各种自定义枚举器
- flutter:即使shouldRepaint()返回true,自定义画家也不会重新绘制
- 自定义先决条件对移动分配运算符有效吗
- 使用VS Code和CMake Tools运行自定义命令
- 如何创建从Maya(或类似程序)到虚幻引擎的自定义数据导出插件
- std::ranges::elements_view,用于自定义类似元组的数据
- 跟随整数索引列表的自定义类迭代器
- 参数化自定义CMake工具链
- 使用自定义比较函数使用std::sort()对矢量字符串进行排序时出现问题
- 如何在自定义类中启用'auto loops'?
- 使用QJsEngine在Qt中注册自定义类型
- 如何在 std::getline 中自定义分隔符
- 通过自定义分隔符从文件中读取对象数组
- std::矢量到带有自定义分隔符的字符串
- 如何实现自动添加分隔符的自定义粘性操纵器