Reputation: 8802
I'm writing approval tests using the excellent ApprovalTests.cpp library. This library automates generation of "snapshots" of results from a function. Snapshots are generated serializing results of type T
to a file using the ostream& operator<< (T val)
.
This operator has always been the C++ convention for formatting some value to a textual representation. While primitive types support this operator and you can write your own implementation for custom types, there isn't a standard implementation for STL containers like std::vector
.
You can implement your own, even using other libraries like fmt
or pprint
. Here are some example with similar outputs. I use the generic type STREAM
as parameter instead of the concrete type ostream
as recommended by ApprovalTests.cpp but the idea doesn't change.
template <typename STREAM, typename T> STREAM& operator<<(STREAM& os, const std::vector<T>& vec) {
os << "[";
for (const auto& x : vec) {
os << x << ", ";
}
os << "]";
return os;
}
template <typename T> std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
using namespace std;
os << "[";
copy(v.begin(), v.end(), ostream_iterator<T>(os, ", "));
os << "]";
return os;
}
template <typename STREAM, typename T> STREAM& operator<<(STREAM& os, const std::vector<T>& vec) {
fmt::print(os, "[{}]", fmt::join(vec, ", "));
return os;
}
With <fmt/ranges.h
header:
template <typename STREAM, typename T> STREAM& operator<<(STREAM& os, const std::vector<T>& vec) {
fmt::print(os, "{}", vec);
return os;
}
https://github.com/p-ranav/pprint
template <typename STREAM, typename T> STREAM& operator<<(STREAM& os, const std::vector<T>& vec) {
pprint::PrettyPrinter printer{os};
printer.print(vec);
return os;
}
Just include prettyprint.hpp and it works for STL containers.
This seems to be the simplest solution, but has the same problem as other solutions, it may break other code.
After some experience with Rust, I find tedious to do this for every C++ STL container. Doing this may break other code where, for example, the same operator has been overloaded for vector.
In Rust you can just add #[Debug]
over the struct
you want to format to text and it can be automatically converted to a textual representation, or implement the trait yourself if you need some non-canonical representation. It is responsibility of a struct author to define its Debug
implementation. This is why every container in the Rust standard library has its own Debug
implementation.
I'm asking if some convention exists in C++ or there is some similar proposal for the standard. It could be useful for approval tests, like in my case, but also for logging or debugging (the debugger could use this formatter to show a variable value to the user).
Upvotes: 4
Views: 475
Reputation: 55564
I'm not aware of any convention or standard proposal for printing containers. However the {fmt} library can print anything range- and tuple-like: https://fmt.dev/latest/api.html#ranges-and-tuple-formatting so you could probably integrate it with ApprovalTests and avoid defining ostream insertion operators yourself.
Disclaimer: I'm the author of {fmt}.
Upvotes: 3