I'm toying around with shift/io stream operator overloads and I was wondering if there is a way to pass additional arguments to the function, while still defining a default value for simpler syntax?
Considering the simple example:
#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;
}
I'm looking to pass the delimiter character , to the function e.g. perhaps through a stream modifier:
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
I've written a simple class that does this, but the resulting syntax is not very clean (requires forcing member operator overload to be called first):
#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;
}
Is there a way to simplify this, without the need of a helper class, or through the use of conventional stream manipulators?
Minor improvements of the current design
What you are doing is possible and can't be simplified much further. If you want to stick to your current implementation, I recommend fixing the following issues:
Unnecessary copy of entire string
Use
to prevent copying the entire string in the
stringstreamat the end (since C++20).Signed/unsigned comparison
results in a comparison between signed and unsigned, which results in compiler warnings at a sufficient warning level. Prefer
std::size_t i.Unconstrained stream insertion operator
is not constrained and would accept any type, even though it only works for vectors. Prefer:
Using
std::stringinstead ofstd::string_viewresults in the creation of an unnecessary string, possibly involving heap allocation, even when we use a string literal such as
",". Normally we would preferstd::string_view, but here, you are storing this sequence in the class. This can create problems becausestd::string_viewhas no ownership over the string, so the lifetime of the string could expire before you use the view. This problem is inherent to your design and can not be easily resolved.Alternative design
Calling
formatteda stream manipulator would be inaccurate, because stream manipulators are functions which accept and return abasic_ios(or derived type). Yourformattedtype is just some type with an operator overload for<<, but you could just as well do:where
"/"would default to","if no argument is provided. The problem is that we are still not using the stream for outputting the characters, but we first have to create a string, write the vector contents to it, and then write the string into the stream.Instead, we can make a function that accepts the stream as its first parameter and writes directly to it. The advantages include:
#include <sstream>, potentially just including<iosfwd>This design as a regular function has precedent in the standard library as well. A classic examples is
std::getlinewith the signature:With this design, we can create much more concise code, which also resolves the issues of:
std::string_viewstd::stringto store the whole printed vectorFurther generalization for any range
We can generalize this code so that it works for any range that provides an input iterator, not just
std::vector. This means that we can use it for types such asstd::array,std::string,std::vector,std::list, etc.