将字符传递给运算符重载的自定义流操纵器
Custom stream manipulator that passes a character to operator overload
我正在研究 shift/io 流运算符重载,我想知道是否有一种方法可以将额外的参数传递给函数,同时仍然为更简单的语法定义默认值?
考虑这个简单的例子:
#include <vector>
#include <iostream>
inline std::ostream& operator<<(std::ostream& ostream, const std::vector< int >& data) {
ostream << data[0];
for (int idx = 1; idx < data.size(); idx++) {
ostream << "," << data[idx]; // pass ',' as argument?
}
return ostream;
}
我希望将分隔符 ,
传递给函数,例如可能通过流修饰符:
std::cout << std::vector<int>(3, 15) << std::endl; // 15,15,15
std::cout << delimiter(;) << std::vector<int>(3,15) << std::endl; // 15;15;15
我写了一个简单的 class 来执行此操作,但生成的语法不是很干净(需要先强制调用成员运算符重载):
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
class formatted{
public:
explicit formatted(const std::string& sequence = ",") : delimiter(sequence) { }
template < typename T >
std::string operator<<(const T& src) const {
std::stringstream out;
if (src.size()) {
out << src[0];
for (int i = 1; i < src.size(); i++) {
out << delimiter << src[i];
}
}
return out.str();
}
protected:
std::string delimiter;
};
template < typename T >
inline std::ostream& operator<<(std::ostream& ostream, const std::vector< T >& data) {
ostream << (formatted() << data);
return ostream;
}
int main(int argc, char const *argv[]) {
std::vector< int > data(10, 5);
std::cout << data << std::endl; // 5,5,5...5,5
std::cout << (formatted("/") << data) << std::endl; // 5/5/5...5/5
return 0;
}
有没有一种方法可以简化这一点,而不需要助手 class,或者通过使用传统的流操纵器?
当前设计的小改进
您正在做的事情是可行的,无法进一步简化。如果您想坚持当前的实施,我建议解决以下问题:
整个字符串的不必要副本
使用
return std::move(out).str();
以防止在末尾复制 stringstream
中的整个字符串 (C++20 起)。
Signed/unsigned比较
for (int i = 1; i < data.size(); ...)
导致有符号和无符号之间的比较,这导致编译器警告达到足够的警告级别。
更喜欢 std::size_t i
.
无约束流插入运算符
operator<<(const T&)
不受约束,可以接受任何类型,即使它只适用于向量。
偏好:
operator<<(const std::vector<T>&)
使用 std::string
代替 std::string_view
const std::string& sequence = ","
导致创建不必要的字符串,可能涉及堆分配,即使我们使用 ","
等字符串文字也是如此。
通常我们更喜欢 std::string_view
,但在这里,您将此序列存储在 class 中。这可能会产生问题,因为 std::string_view
对字符串没有所有权,因此字符串的生命周期可能会在您使用视图之前到期。
这个问题是你的设计固有的,不容易解决。
替代设计
调用 formatted
流操纵器是不准确的,因为流操纵器是接受 return 和 basic_ios
(或派生类型)的函数。您的 formatted
类型只是某种带有 <<
运算符重载的类型,但您也可以这样做:
std::cout << vector_to_string(data, "/");
如果没有提供参数,"/"
将默认为 ","
。
问题是我们仍然没有使用流来输出字符,但我们首先必须创建一个字符串,将向量内容写入其中,然后将字符串写入流。
相反,我们可以制作一个接受流作为其第一个参数并直接写入的函数。
优势包括:
- 删除
#include <sstream>
,可能只包括 <iosfwd>
- 性能提升
- 可能没有堆分配
- 没有不必要的字符串复制(C++20 之前)
- 总体代码更短
这种作为常规函数的设计在标准库中也有先例。 classic 示例是 std::getline
,签名为:
template < class CharT, ...>
std::basic_istream<CharT, ...>& getline( std::basic_istream<CharT, ...>& input,
std::basic_string<CharT, ...>& str,
CharT delim );
通过这种设计,我们可以创建更简洁的代码,这也解决了以下问题:
- 无法使用
std::string_view
- 必须使用
std::string
来存储整个打印矢量
#include <string>
#include <vector>
#include <iostream>
template < typename T >
std::ostream& print(std::ostream& ostream,
const std::vector< T >& data,
std::string_view delimiter = ",")
{
if (not data.empty()) {
ostream << data.front();
for (std::size_t i = 1; i < data.size(); i++) {
ostream << delimiter << data[i];
}
}
return ostream;
}
int main(int argc, char const *argv[])
{
std::vector< int > data(10, 5);
print(std::cout, data) << std::endl;
print(std::cout, data, "/") << std::endl;
}
任何范围的进一步概括
我们可以概括此代码,使其适用于提供输入迭代器的任何范围,而不仅仅是 std::vector
。
这意味着我们可以将它用于 std::array
、std::string
、std::vector
、std::list
等类型
template <typename Range>
std::ostream& print(std::ostream& ostream,
const Range& range,
std::string_view delimiter = ",")
{
auto begin = range.begin();
auto end = range.end();
if (begin != end) {
ostream << *begin;
while (++begin != end) {
ostream << delimiter << *begin;
}
}
return ostream;
}
一些操作输入/输出的函数
#include <iostream>
#include <vector>
#include <string>
std::string Delimiter=" ";
template <typename T>
inline std::ostream& operator<<(std::ostream& ostream, const std::vector<T>& v) {
if (!v.empty()) {
std::ostream_iterator<T> ost(ostream, Delimiter.data());
if (v.size() > 1)
std::copy(begin(v), end(v) - 1, ost);
ostream << v.back();
}
return ostream;
}
int main() {
std::cout << std::vector<int>(3, 15) << std::endl;
Delimiter = "!";
std::cout << std::vector<int>(5, 15) << std::endl;
return 0;
}
当然 Delimiter 变量不是很漂亮但是....
输出
15 15 15
15!15!15!15!15
我正在研究 shift/io 流运算符重载,我想知道是否有一种方法可以将额外的参数传递给函数,同时仍然为更简单的语法定义默认值?
考虑这个简单的例子:
#include <vector>
#include <iostream>
inline std::ostream& operator<<(std::ostream& ostream, const std::vector< int >& data) {
ostream << data[0];
for (int idx = 1; idx < data.size(); idx++) {
ostream << "," << data[idx]; // pass ',' as argument?
}
return ostream;
}
我希望将分隔符 ,
传递给函数,例如可能通过流修饰符:
std::cout << std::vector<int>(3, 15) << std::endl; // 15,15,15
std::cout << delimiter(;) << std::vector<int>(3,15) << std::endl; // 15;15;15
我写了一个简单的 class 来执行此操作,但生成的语法不是很干净(需要先强制调用成员运算符重载):
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
class formatted{
public:
explicit formatted(const std::string& sequence = ",") : delimiter(sequence) { }
template < typename T >
std::string operator<<(const T& src) const {
std::stringstream out;
if (src.size()) {
out << src[0];
for (int i = 1; i < src.size(); i++) {
out << delimiter << src[i];
}
}
return out.str();
}
protected:
std::string delimiter;
};
template < typename T >
inline std::ostream& operator<<(std::ostream& ostream, const std::vector< T >& data) {
ostream << (formatted() << data);
return ostream;
}
int main(int argc, char const *argv[]) {
std::vector< int > data(10, 5);
std::cout << data << std::endl; // 5,5,5...5,5
std::cout << (formatted("/") << data) << std::endl; // 5/5/5...5/5
return 0;
}
有没有一种方法可以简化这一点,而不需要助手 class,或者通过使用传统的流操纵器?
当前设计的小改进
您正在做的事情是可行的,无法进一步简化。如果您想坚持当前的实施,我建议解决以下问题:
整个字符串的不必要副本
使用
return std::move(out).str();
以防止在末尾复制 stringstream
中的整个字符串 (C++20 起)。
Signed/unsigned比较
for (int i = 1; i < data.size(); ...)
导致有符号和无符号之间的比较,这导致编译器警告达到足够的警告级别。
更喜欢 std::size_t i
.
无约束流插入运算符
operator<<(const T&)
不受约束,可以接受任何类型,即使它只适用于向量。 偏好:
operator<<(const std::vector<T>&)
使用 std::string
代替 std::string_view
const std::string& sequence = ","
导致创建不必要的字符串,可能涉及堆分配,即使我们使用 ","
等字符串文字也是如此。
通常我们更喜欢 std::string_view
,但在这里,您将此序列存储在 class 中。这可能会产生问题,因为 std::string_view
对字符串没有所有权,因此字符串的生命周期可能会在您使用视图之前到期。
这个问题是你的设计固有的,不容易解决。
替代设计
调用 formatted
流操纵器是不准确的,因为流操纵器是接受 return 和 basic_ios
(或派生类型)的函数。您的 formatted
类型只是某种带有 <<
运算符重载的类型,但您也可以这样做:
std::cout << vector_to_string(data, "/");
如果没有提供参数,"/"
将默认为 ","
。
问题是我们仍然没有使用流来输出字符,但我们首先必须创建一个字符串,将向量内容写入其中,然后将字符串写入流。
相反,我们可以制作一个接受流作为其第一个参数并直接写入的函数。 优势包括:
- 删除
#include <sstream>
,可能只包括<iosfwd>
- 性能提升
- 可能没有堆分配
- 没有不必要的字符串复制(C++20 之前)
- 总体代码更短
这种作为常规函数的设计在标准库中也有先例。 classic 示例是 std::getline
,签名为:
template < class CharT, ...>
std::basic_istream<CharT, ...>& getline( std::basic_istream<CharT, ...>& input,
std::basic_string<CharT, ...>& str,
CharT delim );
通过这种设计,我们可以创建更简洁的代码,这也解决了以下问题:
- 无法使用
std::string_view
- 必须使用
std::string
来存储整个打印矢量
#include <string>
#include <vector>
#include <iostream>
template < typename T >
std::ostream& print(std::ostream& ostream,
const std::vector< T >& data,
std::string_view delimiter = ",")
{
if (not data.empty()) {
ostream << data.front();
for (std::size_t i = 1; i < data.size(); i++) {
ostream << delimiter << data[i];
}
}
return ostream;
}
int main(int argc, char const *argv[])
{
std::vector< int > data(10, 5);
print(std::cout, data) << std::endl;
print(std::cout, data, "/") << std::endl;
}
任何范围的进一步概括
我们可以概括此代码,使其适用于提供输入迭代器的任何范围,而不仅仅是 std::vector
。
这意味着我们可以将它用于 std::array
、std::string
、std::vector
、std::list
等类型
template <typename Range>
std::ostream& print(std::ostream& ostream,
const Range& range,
std::string_view delimiter = ",")
{
auto begin = range.begin();
auto end = range.end();
if (begin != end) {
ostream << *begin;
while (++begin != end) {
ostream << delimiter << *begin;
}
}
return ostream;
}
一些操作输入/输出的函数
#include <iostream>
#include <vector>
#include <string>
std::string Delimiter=" ";
template <typename T>
inline std::ostream& operator<<(std::ostream& ostream, const std::vector<T>& v) {
if (!v.empty()) {
std::ostream_iterator<T> ost(ostream, Delimiter.data());
if (v.size() > 1)
std::copy(begin(v), end(v) - 1, ost);
ostream << v.back();
}
return ostream;
}
int main() {
std::cout << std::vector<int>(3, 15) << std::endl;
Delimiter = "!";
std::cout << std::vector<int>(5, 15) << std::endl;
return 0;
}
当然 Delimiter 变量不是很漂亮但是.... 输出
15 15 15
15!15!15!15!15