Skip to content

Instantly share code, notes, and snippets.

@elmotec
Last active February 19, 2022 14:27
Show Gist options
  • Save elmotec/e25190fb9de64a7340c47c71272ea367 to your computer and use it in GitHub Desktop.
Save elmotec/e25190fb9de64a7340c47c71272ea367 to your computer and use it in GitHub Desktop.
/** Convenience functions to stream output from std:: data structures.
*
* Useful when you do not have a debugger. See test below to get a sense of
* what's available.
*
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
/// Holds display preferences to stream out T
template <typename T>
struct Printer {
T ref_; //< handle to the underlying data structure.
std::string sep_ = ", "s; //< separator for container.
size_t maxElements_ = 7; //< max number of elements in a container.
/** @defgroup fluent setters to build the p object
* @{ */
Printer<T>& sep(std::string sep) {
sep_ = sep;
return *this;
}
Printer<T>& max(size_t maxElements) {
maxElements_ = maxElements;
return *this;
}
/** @} */
};
template <typename T>
Printer<T> p(const T& val) {
return Printer<T>{val};
}
template <typename IT>
struct PrinterIt {
IT begin_;
IT end_;
};
template <typename IT>
Printer<PrinterIt<IT>> p(IT begin, IT end) {
return p(PrinterIt<IT>{begin, end});
}
/// Defaults to pass through output.
template <typename T>
std::ostream& operator<<(std::ostream& os, const Printer<T>& j) {
return os << j.ref_;
}
/// Strings and const char * are double quoted.
std::ostream& operator<<(std::ostream& os, const Printer<std::string>& j) {
return os << '"' << j.ref_ << '"';
}
/// Pairs displayed separated by colon with an eye on maps.
template <typename U, typename V>
std::ostream& operator<<(std::ostream& os, const Printer<std::pair<U, V>>& j) {
return os << p(j.ref_.first) << ": " << p(j.ref_.second);
}
/// Factors iteration of containers.
template <typename IT>
std::ostream& operator<<(std::ostream& os, const Printer<PrinterIt<IT>>& j) {
size_t count = 0;
auto it = j.ref_.begin_;
auto finish = j.ref_.end_;
auto prev = it;
while (it != finish) {
if (count < j.maxElements_ - 1) os << p(*it);
prev = it;
it++;
count++;
if (it == finish) {
if (count == j.maxElements_) os << j.sep_ << p(*prev);
if (count > j.maxElements_) os << " ... " << p(*prev);
} else if (count < j.maxElements_ - 1) {
os << j.sep_;
}
}
return os;
}
#define COMMA ,
#define OUTPUT_OPERATOR(DATASTRUCT, OPEN, CLOSE) \
std::ostream& operator<<(std::ostream& os, const Printer<DATASTRUCT>& j) { \
os << OPEN; \
os << p(begin(j.ref_), end(j.ref_)).sep(j.sep_).max(j.maxElements_); \
os << CLOSE; \
return os; \
};
template <typename T>
OUTPUT_OPERATOR(std::vector<T>, "[", "]");
template <typename T>
OUTPUT_OPERATOR(std::set<T>, "{", "}");
template <typename T>
OUTPUT_OPERATOR(std::queue<T>, "[", "]");
template <typename T>
OUTPUT_OPERATOR(std::list<T>, "[", "]");
template <typename K, typename V>
OUTPUT_OPERATOR(std::map<K COMMA V>, "{", "}");
template <typename K, typename V>
OUTPUT_OPERATOR(std::unordered_map<K COMMA V>, "{", "}");
/** Beyond this points are the tests. */
TEST(printer, outputString) {
stringstream actual;
// note the s-suffix to make it a std::string!
actual << p("hello"s);
ASSERT_THAT(actual.str(), testing::Eq(R"("hello")"));
}
// FIXME: C string are interpreted as char[] with variable number of chars.
// Not sure how to handle that right now, so let's stick to std::string (s
// suffix). TEST(printer, outputCString) { stringstream actual; actual <<
// p("hello"); ASSERT_THAT(actual.str(), testing::Eq(R"("hello")"));
//}
TEST(printer, outputFloat) {
stringstream actual;
actual << p(1.0 / 3);
ASSERT_THAT(actual.str(), testing::Eq("0.333333"));
}
TEST(printer, outputFloatWithManip) {
stringstream actual;
actual << setprecision(2) << p(1.0 / 3);
ASSERT_THAT(actual.str(), testing::Eq("0.33"));
}
TEST(printer, outputVector) {
stringstream actual;
actual << p(vector<int>{1, 2, 3});
ASSERT_THAT(actual.str(), testing::Eq("[1, 2, 3]"));
}
TEST(printer, outputEmptyVector) {
stringstream actual;
actual << p(vector<int>());
ASSERT_THAT(actual.str(), testing::Eq("[]"));
}
TEST(printer, shortenLongContainers) {
vector<int> v(20);
iota(begin(v), end(v), 0);
stringstream actual;
actual << p(v);
ASSERT_THAT(actual.str(), testing::Eq("[0, 1, 2, 3, 4, 5 ... 19]"));
}
TEST(printer, doNotshortenLongContainersAtLimit) {
vector<int> v(7);
iota(begin(v), end(v), 0);
stringstream actual;
actual << p(v);
ASSERT_THAT(actual.str(), testing::Eq("[0, 1, 2, 3, 4, 5, 6]"));
}
TEST(printer, shortenLongContainersRightAfterLimit) {
vector<int> v(8);
iota(begin(v), end(v), 0);
stringstream actual;
actual << p(v);
ASSERT_THAT(actual.str(), testing::Eq("[0, 1, 2, 3, 4, 5 ... 7]"));
}
TEST(printer, outputVectorOfVector) {
vector<vector<int>> v{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
stringstream actual;
actual << p(v).sep("\n ");
ASSERT_THAT(actual.str(), testing::Eq("[[1, 2, 3]\n [4, 5, 6]\n [7, 8, 9]]"));
}
TEST(printer, outputEmptyMap) {
stringstream actual;
actual << p(map<int, int>());
ASSERT_THAT(actual.str(), testing::Eq("{}"));
}
TEST(printer, outputPairOfString) {
stringstream actual;
actual << p(make_pair("first"s, "second"s));
ASSERT_THAT(actual.str(), testing::Eq(R"("first": "second")"));
}
TEST(printer, outputMap) {
map<string, string> m{
{"key1"s, "value1"s}, {"key2"s, "value2"s}, {"key3"s, ""s}};
stringstream actual;
actual << p(m);
ASSERT_THAT(
actual.str(),
testing::Eq(R"({"key1": "value1", "key2": "value2", "key3": ""})"));
}
TEST(printer, outputMapWithNewline) {
map<string, string> m{{"key1"s, "value1"s}, {"key2"s, "value2"s}};
stringstream actual;
actual << p(m).sep(",\n ");
ASSERT_THAT(actual.str(),
testing::Eq("{\"key1\": \"value1\",\n \"key2\": \"value2\"}"));
}
TEST(printer, outputVectorOfMap) {
vector<map<string, int>> v{
map<string, int>{{"three", 3}, {"five", 5}, {"eight", 8}},
map<string, int>{{"eins", 1}, {"zwei", 2}}};
stringstream actual;
actual << p(v);
ASSERT_THAT(
actual.str(),
testing::Eq(
R"([{"eight": 8, "five": 5, "three": 3}, {"eins": 1, "zwei": 2}])"));
}
TEST(printer, outputSet) {
set<int> s{1, 2, 3};
stringstream actual;
actual << p(s);
ASSERT_THAT(actual.str(), testing::Eq("{1, 2, 3}"));
}
TEST(printer, outputList) {
list<int> s{1, 2, 3};
stringstream actual;
actual << p(s);
ASSERT_THAT(actual.str(), testing::Eq("[1, 2, 3]"));
}
TEST(printer, outputContainerViaIterators) {
vector<int> s{1, 2, 3, 4};
stringstream actual;
actual << p(begin(s), begin(s) + 2);
ASSERT_THAT(actual.str(), testing::Eq("1, 2"));
}
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment