Skip to content

Commit

Permalink
Merge pull request sxs-collaboration#5845 from kidder/output_unstream…
Browse files Browse the repository at this point in the history
…able

Enable printing containers holding non-streamable objects
  • Loading branch information
wthrowe authored Mar 15, 2024
2 parents 348ab00 + b153ea5 commit 034a5d7
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 23 deletions.
9 changes: 4 additions & 5 deletions src/DataStructures/DataBox/DataBox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "Utilities/Gsl.hpp"
#include "Utilities/NoSuchType.hpp"
#include "Utilities/Overloader.hpp"
#include "Utilities/PrintHelpers.hpp"
#include "Utilities/Requires.hpp"
#include "Utilities/Serialization/Serialize.hpp"
#include "Utilities/TMPL.hpp"
Expand Down Expand Up @@ -535,11 +536,9 @@ std::string DataBox<tmpl::list<Tags...>>::print_items() const {
os << "----------\n";
os << "Name: " << pretty_type::get_name<tag>() << "\n";
os << "Type: " << pretty_type::get_name<type>() << "\n";
if constexpr (tt::is_streamable_v<std::ostringstream, type>) {
os << "Value: " << this->get<tag>() << "\n";
} else {
os << "Value: UNSTREAMABLE\n";
}
os << "Value: ";
print_value(os, this->get<tag>());
os << "\n";
};
tmpl::for_each<mutable_item_creation_tags>(print_item);
if constexpr (PrintImmutableItems) {
Expand Down
6 changes: 5 additions & 1 deletion src/DataStructures/FixedHashMap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,11 @@ std::ostream& operator<<(
[](std::ostream& out,
const typename FixedHashMap<MaxSize, Key, ValueType, Hash,
KeyEqual>::value_type& entry) {
out << "[" << entry.first << "," << entry.second << "]";
out << "[";
print_value(out, entry.first);
out << ",";
print_value(out, entry.second);
out << "]";
});
return os;
}
19 changes: 17 additions & 2 deletions src/Utilities/PrintHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
#include <utility>
#include <vector>

#include "Utilities/Requires.hpp"
#include "Utilities/TypeTraits/IsStreamable.hpp"

/// \ingroup UtilitiesGroup
/// \brief Either streams `value` or the string "UNSTREAMABLE"
template <typename StreamType, typename T>
StreamType& print_value(StreamType& os, const T& value) {
if constexpr (tt::is_streamable_v<StreamType, T>) {
os << value;
} else {
os << "UNSTREAMABLE";
}
return os;
}

/*!
* \ingroup UtilitiesGroup
* \brief Applies the function f(out, it) to each item from begin to
Expand Down Expand Up @@ -42,7 +57,7 @@ void sequence_print_helper(std::ostream& out, ForwardIt begin,
out, std::move(begin), end,
[](std::ostream& os,
const typename std::iterator_traits<ForwardIt>::value_type& it) {
os << it;
print_value(os, it);
});
}

Expand Down Expand Up @@ -71,7 +86,7 @@ void unordered_print_helper(std::ostream& out, ForwardIt begin,
out, std::move(begin), end,
[](std::ostream& os,
const typename std::iterator_traits<ForwardIt>::value_type& it) {
os << it;
print_value(os, it);
});
}
/// @}
32 changes: 24 additions & 8 deletions src/Utilities/StdHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
#include "Utilities/TypeTraits/IsStreamable.hpp"

namespace StdHelpers_detail {

// Helper classes for operator<< for tuples
template <size_t N>
struct TuplePrinter {
template <typename... Args>
static std::ostream& print(std::ostream& os, const std::tuple<Args...>& t) {
TuplePrinter<N - 1>::print(os, t);
os << "," << std::get<N - 1>(t);
os << ",";
print_value(os, std::get<N - 1>(t));
return os;
}
};
Expand All @@ -45,7 +47,7 @@ template <>
struct TuplePrinter<1> {
template <typename... Args>
static std::ostream& print(std::ostream& os, const std::tuple<Args...>& t) {
os << std::get<0>(t);
print_value(os, std::get<0>(t));
return os;
}
};
Expand Down Expand Up @@ -124,7 +126,11 @@ inline std::ostream& operator<<(std::ostream& os,
os, begin(m), end(m),
[](std::ostream& out,
const typename std::unordered_map<K, V, H>::value_type& value) {
out << "[" << value.first << "," << value.second << "]";
out << "[";
print_value(out, value.first);
out << ",";
print_value(out, value.second);
out << "]";
});
return os;
}
Expand All @@ -139,7 +145,11 @@ inline std::ostream& operator<<(std::ostream& os, const std::map<K, V, C>& m) {
os, begin(m), end(m),
[](std::ostream& out,
const typename std::map<K, V, C>::value_type& value) {
out << "[" << value.first << "," << value.second << "]";
out << "[";
print_value(out, value.first);
out << ",";
print_value(out, value.second);
out << "]";
});
return os;
}
Expand Down Expand Up @@ -200,7 +210,12 @@ inline std::ostream& operator<<(std::ostream& os, const std::shared_ptr<T>& t) {
*/
template <typename T, typename U>
inline std::ostream& operator<<(std::ostream& os, const std::pair<T, U>& t) {
return os << "(" << t.first << ", " << t.second << ")";
os << "(";
print_value(os, t.first);
os << ", ";
print_value(os, t.second);
os << ")";
return os;
}

/*!
Expand All @@ -211,7 +226,8 @@ template <typename T>
inline std::ostream& operator<<(std::ostream& os, const std::optional<T>& t) {
// Match boost::optional behavior and print "--" when invalid
if (t.has_value()) {
return os << t.value();
print_value(os, t.value());
return os;
} else {
return os << "--";
}
Expand Down Expand Up @@ -241,7 +257,7 @@ inline std::string keys_of(const std::unordered_map<K, V, H>& m) {
os, begin(m), end(m),
[](std::ostream& out,
const typename std::unordered_map<K, V, H>::value_type& value) {
out << value.first;
print_value(out, value.first);
});
return os.str();
}
Expand All @@ -257,7 +273,7 @@ inline std::string keys_of(const std::map<K, V, C>& m) {
os, begin(m), end(m),
[](std::ostream& out,
const typename std::map<K, V, C>::value_type& value) {
out << value.first;
print_value(out, value.first);
});
return os.str();
}
Expand Down
11 changes: 4 additions & 7 deletions src/Utilities/TaggedTuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@

#include "Utilities/Overloader.hpp"
#include "Utilities/PrettyType.hpp"
#include "Utilities/PrintHelpers.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TypeTraits.hpp"
#include "Utilities/TypeTraits/IsStreamable.hpp"

/// \cond
namespace PUP {
Expand Down Expand Up @@ -704,11 +703,9 @@ std::ostream& operator<<(std::ostream& os, const TaggedTuple<Tags...>& t) {
os << "----------\n";
os << "Name: " << pretty_type::get_name<tag>() << "\n";
os << "Type: " << pretty_type::get_name<type>() << "\n";
if constexpr (tt::is_streamable_v<std::ostringstream, type>) {
os << "Value: " << get<tag>(t) << "\n";
} else {
os << "Value: UNSTREAMABLE\n";
}
os << "Value: ";
print_value(os, get<tag>(t));
os << "\n";
};
tmpl::for_each<tmpl::list<Tags...>>(print_item);
return os;
Expand Down
21 changes: 21 additions & 0 deletions tests/Unit/Framework/TestHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,27 @@ numerical_derivative(const Invocable& function,
(0.75 / delta) * (function(x_1ahead) - function(x_1behind));
}

struct NonStreamable {
int value{0};
};

template <>
struct std::hash<NonStreamable> {
std::size_t operator()(const NonStreamable& ns) const {
return std::hash<int>{}(ns.value);
}
};

inline bool operator==(const NonStreamable& a, const NonStreamable& b) {
return a.value == b.value;
}
inline bool operator!=(const NonStreamable& a, const NonStreamable& b) {
return not(a == b);
}
inline bool operator<(const NonStreamable& a, const NonStreamable& b) {
return a.value < b.value;
}

struct NonCopyable {
constexpr NonCopyable() = default;
constexpr NonCopyable(const NonCopyable&) = delete;
Expand Down
83 changes: 83 additions & 0 deletions tests/Unit/Utilities/Test_StdHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,51 +20,87 @@
#include <utility>
#include <vector>

#include "Framework/TestHelpers.hpp"
#include "Utilities/GetOutput.hpp"
#include "Utilities/StdHelpers.hpp"

// IWYU pragma: no_forward_declare boost::hash

SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") {
NonStreamable ns{2};
NonStreamable another_ns{4};

std::list<int> my_list;
CHECK(get_output(my_list) == "()");
my_list = {1};
CHECK(get_output(my_list) == "(1)");
my_list = {{1, 2, 3, 4, 5}};
CHECK(get_output(my_list) == "(1,2,3,4,5)");
std::list<NonStreamable> ns_list{};
CHECK(get_output(ns_list) == "()");
ns_list = {ns};
CHECK(get_output(ns_list) == "(UNSTREAMABLE)");
ns_list = {{ns, ns}};
CHECK(get_output(ns_list) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::vector<int> my_vector;
CHECK(get_output(my_vector) == "()");
my_vector = {1};
CHECK(get_output(my_vector) == "(1)");
my_vector = {{1, 2, 3, 4, 5}};
CHECK(get_output(my_vector) == "(1,2,3,4,5)");
std::vector<NonStreamable> ns_vector{};
CHECK(get_output(ns_vector) == "()");
ns_vector = {ns};
CHECK(get_output(ns_vector) == "(UNSTREAMABLE)");
ns_vector = {{ns, ns}};
CHECK(get_output(ns_vector) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::deque<int> my_deque;
CHECK(get_output(my_deque) == "()");
my_deque = {1};
CHECK(get_output(my_deque) == "(1)");
my_deque = {{1, 2, 3, 4, 5}};
CHECK(get_output(my_deque) == "(1,2,3,4,5)");
std::deque<NonStreamable> ns_deque;
CHECK(get_output(ns_deque) == "()");
ns_deque = {ns};
CHECK(get_output(ns_deque) == "(UNSTREAMABLE)");
ns_deque = {{ns, ns}};
CHECK(get_output(ns_deque) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::array<int, 0> a0{};
CHECK(get_output(a0) == "()");
std::array<int, 1> a1{{1}};
CHECK(get_output(a1) == "(1)");
std::array<int, 5> a5{{1, 2, 3, 4, 5}};
CHECK(get_output(a5) == "(1,2,3,4,5)");
std::array<NonStreamable, 0> ns_array0{};
CHECK(get_output(ns_array0) == "()");
std::array<NonStreamable, 1> ns_array1{{ns}};
CHECK(get_output(ns_array1) == "(UNSTREAMABLE)");
std::array<NonStreamable, 2> ns_array2 = {{ns, ns}};
CHECK(get_output(ns_array2) == "(UNSTREAMABLE,UNSTREAMABLE)");

auto tuple1 = std::make_tuple<int, double, std::string>(1, 1.87, "test");
CHECK(get_output(tuple1) == "(1,1.87,test)");
std::tuple<> tuple0{};
CHECK(get_output(tuple0) == "()");
auto tuple_ns = std::make_tuple<int, double, NonStreamable>(1, 1.87, {});
CHECK(get_output(tuple_ns) == "(1,1.87,UNSTREAMABLE)");

std::optional<int> opt{};
CHECK(get_output(opt) == "--");
opt = -42;
CHECK(get_output(opt) == "-42");
opt = std::nullopt;
CHECK(get_output(opt) == "--");
std::optional<NonStreamable> opt_ns{};
CHECK(get_output(opt_ns) == "--");
opt_ns = ns;
CHECK(get_output(opt_ns) == "UNSTREAMABLE");
opt_ns = std::nullopt;
CHECK(get_output(opt_ns) == "--");

std::unordered_map<std::string, int, boost::hash<std::string>>
my_unordered_map;
Expand All @@ -80,6 +116,26 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") {
CHECK(get_output(my_unordered_map) ==
"([aaa,1],[bbb,2],[ccc,3],[ddd,4],[eee,5])");
CHECK(keys_of(my_unordered_map) == "(aaa,bbb,ccc,ddd,eee)");
std::unordered_map<int, NonStreamable> ns_value_unordered_map;
CHECK(get_output(ns_value_unordered_map) == "()");
CHECK(keys_of(ns_value_unordered_map) == "()");
ns_value_unordered_map[1] = ns;
CHECK(get_output(ns_value_unordered_map) == "([1,UNSTREAMABLE])");
CHECK(keys_of(ns_value_unordered_map) == "(1)");
ns_value_unordered_map[3] = ns;
CHECK(get_output(ns_value_unordered_map) ==
"([1,UNSTREAMABLE],[3,UNSTREAMABLE])");
CHECK(keys_of(ns_value_unordered_map) == "(1,3)");
std::unordered_map<NonStreamable, int> ns_key_unordered_map;
CHECK(get_output(ns_key_unordered_map) == "()");
CHECK(keys_of(ns_key_unordered_map) == "()");
ns_key_unordered_map[ns] = 1;
CHECK(get_output(ns_key_unordered_map) == "([UNSTREAMABLE,1])");
CHECK(keys_of(ns_key_unordered_map) == "(UNSTREAMABLE)");
ns_key_unordered_map[NonStreamable{4}] = 3;
CHECK(get_output(ns_key_unordered_map) ==
"([UNSTREAMABLE,1],[UNSTREAMABLE,3])");
CHECK(keys_of(ns_key_unordered_map) == "(UNSTREAMABLE,UNSTREAMABLE)");

// check map with some other comparison op
std::map<std::string, int, std::greater<>> my_map;
Expand All @@ -95,19 +151,44 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") {
CHECK(get_output(my_map) == "([eee,5],[ddd,4],[ccc,3],[bbb,2],[aaa,1])");
CHECK(keys_of(my_map) == "(eee,ddd,ccc,bbb,aaa)");

std::map<int, NonStreamable> ns_value_map;
CHECK(get_output(ns_value_map) == "()");
CHECK(keys_of(ns_value_map) == "()");
ns_value_map[1] = ns;
CHECK(get_output(ns_value_map) == "([1,UNSTREAMABLE])");
CHECK(keys_of(ns_value_map) == "(1)");
ns_value_map[3] = ns;
CHECK(get_output(ns_value_map) == "([1,UNSTREAMABLE],[3,UNSTREAMABLE])");
CHECK(keys_of(ns_value_map) == "(1,3)");
std::map<NonStreamable, int> ns_key_map;
CHECK(get_output(ns_key_map) == "()");
CHECK(keys_of(ns_key_map) == "()");
ns_key_map[ns] = 1;
CHECK(get_output(ns_key_map) == "([UNSTREAMABLE,1])");
CHECK(keys_of(ns_key_map) == "(UNSTREAMABLE)");
ns_key_map[NonStreamable{4}] = 3;
CHECK(get_output(ns_key_map) == "([UNSTREAMABLE,1],[UNSTREAMABLE,3])");
CHECK(keys_of(ns_key_map) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::unordered_set<int> my_unordered_set{1, 3, 4, 5};
CHECK(get_output(my_unordered_set) == "(1,3,4,5)");
std::unordered_set<int, boost::hash<int>> my_boost_unordered_set{1, 3, 4, 5};
CHECK(get_output(my_boost_unordered_set) == "(1,3,4,5)");
std::unordered_set<NonStreamable> ns_unordered_set{ns, another_ns};
CHECK(get_output(ns_unordered_set) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::unordered_multiset<int> my_unordered_multiset{1, 3, 1, 5};
CHECK(get_output(my_unordered_multiset) == "(1,1,3,5)");
std::unordered_multiset<int, boost::hash<int>> my_boost_unordered_multiset{
1, 3, 1, 5};
CHECK(get_output(my_boost_unordered_multiset) == "(1,1,3,5)");
std::unordered_multiset<NonStreamable> ns_unordered_multiset{ns, ns};
CHECK(get_output(ns_unordered_multiset) == "(UNSTREAMABLE,UNSTREAMABLE)");

std::set<int> my_set{1, 3, 4, 5};
CHECK(get_output(my_set) == "(1,3,4,5)");
std::unordered_set<NonStreamable> ns_set{ns, another_ns};
CHECK(get_output(ns_set) == "(UNSTREAMABLE,UNSTREAMABLE)");

auto my_unique = std::make_unique<double>(6.7);
CHECK("6.7" == get_output(my_unique));
Expand All @@ -116,6 +197,8 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") {

auto my_pair = std::make_pair(7.8, "test"s);
CHECK("(7.8, test)" == get_output(my_pair));
auto ns_pair = std::make_pair(7.8, ns);
CHECK("(7.8, UNSTREAMABLE)" == get_output(ns_pair));

CHECK("1.19e+01 10 test" ==
formatted_string("%1.2e %d %s", 11.87, 10, "test"));
Expand Down

0 comments on commit 034a5d7

Please sign in to comment.