From b153ea5e01cd135515ac0ab0339a852bb95df81d Mon Sep 17 00:00:00 2001 From: Lawrence Kidder Date: Tue, 12 Mar 2024 17:53:29 -0400 Subject: [PATCH] Enable printing containers holding non-streamable objects Instead of failing to compile, this will print UNSTREAMABLE as the value of a non-streamable object. This is useful for debugging. For example, you can print the contents of a DataBox without having to write stream operators for all the types of items in the DataBox. --- src/DataStructures/DataBox/DataBox.hpp | 9 ++- src/DataStructures/FixedHashMap.hpp | 6 +- src/Utilities/PrintHelpers.hpp | 19 +++++- src/Utilities/StdHelpers.hpp | 32 ++++++--- src/Utilities/TaggedTuple.hpp | 11 ++-- tests/Unit/Framework/TestHelpers.hpp | 21 ++++++ tests/Unit/Utilities/Test_StdHelpers.cpp | 83 ++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 23 deletions(-) diff --git a/src/DataStructures/DataBox/DataBox.hpp b/src/DataStructures/DataBox/DataBox.hpp index ff6db163a7e2..0e6e32ba48ee 100644 --- a/src/DataStructures/DataBox/DataBox.hpp +++ b/src/DataStructures/DataBox/DataBox.hpp @@ -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" @@ -535,11 +536,9 @@ std::string DataBox>::print_items() const { os << "----------\n"; os << "Name: " << pretty_type::get_name() << "\n"; os << "Type: " << pretty_type::get_name() << "\n"; - if constexpr (tt::is_streamable_v) { - os << "Value: " << this->get() << "\n"; - } else { - os << "Value: UNSTREAMABLE\n"; - } + os << "Value: "; + print_value(os, this->get()); + os << "\n"; }; tmpl::for_each(print_item); if constexpr (PrintImmutableItems) { diff --git a/src/DataStructures/FixedHashMap.hpp b/src/DataStructures/FixedHashMap.hpp index 47e2bd2bfb30..7b107c2f29d9 100644 --- a/src/DataStructures/FixedHashMap.hpp +++ b/src/DataStructures/FixedHashMap.hpp @@ -601,7 +601,11 @@ std::ostream& operator<<( [](std::ostream& out, const typename FixedHashMap::value_type& entry) { - out << "[" << entry.first << "," << entry.second << "]"; + out << "["; + print_value(out, entry.first); + out << ","; + print_value(out, entry.second); + out << "]"; }); return os; } diff --git a/src/Utilities/PrintHelpers.hpp b/src/Utilities/PrintHelpers.hpp index 4d4c0c8a164c..b0435c90baf7 100644 --- a/src/Utilities/PrintHelpers.hpp +++ b/src/Utilities/PrintHelpers.hpp @@ -10,6 +10,21 @@ #include #include +#include "Utilities/Requires.hpp" +#include "Utilities/TypeTraits/IsStreamable.hpp" + +/// \ingroup UtilitiesGroup +/// \brief Either streams `value` or the string "UNSTREAMABLE" +template +StreamType& print_value(StreamType& os, const T& value) { + if constexpr (tt::is_streamable_v) { + os << value; + } else { + os << "UNSTREAMABLE"; + } + return os; +} + /*! * \ingroup UtilitiesGroup * \brief Applies the function f(out, it) to each item from begin to @@ -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::value_type& it) { - os << it; + print_value(os, it); }); } @@ -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::value_type& it) { - os << it; + print_value(os, it); }); } /// @} diff --git a/src/Utilities/StdHelpers.hpp b/src/Utilities/StdHelpers.hpp index d8f4fea4ed0b..efc28d980d71 100644 --- a/src/Utilities/StdHelpers.hpp +++ b/src/Utilities/StdHelpers.hpp @@ -30,13 +30,15 @@ #include "Utilities/TypeTraits/IsStreamable.hpp" namespace StdHelpers_detail { + // Helper classes for operator<< for tuples template struct TuplePrinter { template static std::ostream& print(std::ostream& os, const std::tuple& t) { TuplePrinter::print(os, t); - os << "," << std::get(t); + os << ","; + print_value(os, std::get(t)); return os; } }; @@ -45,7 +47,7 @@ template <> struct TuplePrinter<1> { template static std::ostream& print(std::ostream& os, const std::tuple& t) { - os << std::get<0>(t); + print_value(os, std::get<0>(t)); return os; } }; @@ -124,7 +126,11 @@ inline std::ostream& operator<<(std::ostream& os, os, begin(m), end(m), [](std::ostream& out, const typename std::unordered_map::value_type& value) { - out << "[" << value.first << "," << value.second << "]"; + out << "["; + print_value(out, value.first); + out << ","; + print_value(out, value.second); + out << "]"; }); return os; } @@ -139,7 +145,11 @@ inline std::ostream& operator<<(std::ostream& os, const std::map& m) { os, begin(m), end(m), [](std::ostream& out, const typename std::map::value_type& value) { - out << "[" << value.first << "," << value.second << "]"; + out << "["; + print_value(out, value.first); + out << ","; + print_value(out, value.second); + out << "]"; }); return os; } @@ -200,7 +210,12 @@ inline std::ostream& operator<<(std::ostream& os, const std::shared_ptr& t) { */ template inline std::ostream& operator<<(std::ostream& os, const std::pair& t) { - return os << "(" << t.first << ", " << t.second << ")"; + os << "("; + print_value(os, t.first); + os << ", "; + print_value(os, t.second); + os << ")"; + return os; } /*! @@ -211,7 +226,8 @@ template inline std::ostream& operator<<(std::ostream& os, const std::optional& 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 << "--"; } @@ -241,7 +257,7 @@ inline std::string keys_of(const std::unordered_map& m) { os, begin(m), end(m), [](std::ostream& out, const typename std::unordered_map::value_type& value) { - out << value.first; + print_value(out, value.first); }); return os.str(); } @@ -257,7 +273,7 @@ inline std::string keys_of(const std::map& m) { os, begin(m), end(m), [](std::ostream& out, const typename std::map::value_type& value) { - out << value.first; + print_value(out, value.first); }); return os.str(); } diff --git a/src/Utilities/TaggedTuple.hpp b/src/Utilities/TaggedTuple.hpp index 902d0a7fbbde..b3d59ea56b3b 100644 --- a/src/Utilities/TaggedTuple.hpp +++ b/src/Utilities/TaggedTuple.hpp @@ -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 { @@ -704,11 +703,9 @@ std::ostream& operator<<(std::ostream& os, const TaggedTuple& t) { os << "----------\n"; os << "Name: " << pretty_type::get_name() << "\n"; os << "Type: " << pretty_type::get_name() << "\n"; - if constexpr (tt::is_streamable_v) { - os << "Value: " << get(t) << "\n"; - } else { - os << "Value: UNSTREAMABLE\n"; - } + os << "Value: "; + print_value(os, get(t)); + os << "\n"; }; tmpl::for_each>(print_item); return os; diff --git a/tests/Unit/Framework/TestHelpers.hpp b/tests/Unit/Framework/TestHelpers.hpp index b3ba2f0c936c..69a0090b8f6b 100644 --- a/tests/Unit/Framework/TestHelpers.hpp +++ b/tests/Unit/Framework/TestHelpers.hpp @@ -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 { + std::size_t operator()(const NonStreamable& ns) const { + return std::hash{}(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; diff --git a/tests/Unit/Utilities/Test_StdHelpers.cpp b/tests/Unit/Utilities/Test_StdHelpers.cpp index 58caae1d3507..9ac8725f3066 100644 --- a/tests/Unit/Utilities/Test_StdHelpers.cpp +++ b/tests/Unit/Utilities/Test_StdHelpers.cpp @@ -20,18 +20,28 @@ #include #include +#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 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 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 my_vector; CHECK(get_output(my_vector) == "()"); @@ -39,6 +49,12 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") { 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 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 my_deque; CHECK(get_output(my_deque) == "()"); @@ -46,6 +62,12 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") { 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 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 a0{}; CHECK(get_output(a0) == "()"); @@ -53,11 +75,19 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") { CHECK(get_output(a1) == "(1)"); std::array a5{{1, 2, 3, 4, 5}}; CHECK(get_output(a5) == "(1,2,3,4,5)"); + std::array ns_array0{}; + CHECK(get_output(ns_array0) == "()"); + std::array ns_array1{{ns}}; + CHECK(get_output(ns_array1) == "(UNSTREAMABLE)"); + std::array ns_array2 = {{ns, ns}}; + CHECK(get_output(ns_array2) == "(UNSTREAMABLE,UNSTREAMABLE)"); auto tuple1 = std::make_tuple(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(1, 1.87, {}); + CHECK(get_output(tuple_ns) == "(1,1.87,UNSTREAMABLE)"); std::optional opt{}; CHECK(get_output(opt) == "--"); @@ -65,6 +95,12 @@ SPECTRE_TEST_CASE("Unit.Utilities.StdHelpers.Output", "[Utilities][Unit]") { CHECK(get_output(opt) == "-42"); opt = std::nullopt; CHECK(get_output(opt) == "--"); + std::optional 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> my_unordered_map; @@ -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 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 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> my_map; @@ -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 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 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 my_unordered_set{1, 3, 4, 5}; CHECK(get_output(my_unordered_set) == "(1,3,4,5)"); std::unordered_set> my_boost_unordered_set{1, 3, 4, 5}; CHECK(get_output(my_boost_unordered_set) == "(1,3,4,5)"); + std::unordered_set ns_unordered_set{ns, another_ns}; + CHECK(get_output(ns_unordered_set) == "(UNSTREAMABLE,UNSTREAMABLE)"); std::unordered_multiset my_unordered_multiset{1, 3, 1, 5}; CHECK(get_output(my_unordered_multiset) == "(1,1,3,5)"); std::unordered_multiset> my_boost_unordered_multiset{ 1, 3, 1, 5}; CHECK(get_output(my_boost_unordered_multiset) == "(1,1,3,5)"); + std::unordered_multiset ns_unordered_multiset{ns, ns}; + CHECK(get_output(ns_unordered_multiset) == "(UNSTREAMABLE,UNSTREAMABLE)"); std::set my_set{1, 3, 4, 5}; CHECK(get_output(my_set) == "(1,3,4,5)"); + std::unordered_set ns_set{ns, another_ns}; + CHECK(get_output(ns_set) == "(UNSTREAMABLE,UNSTREAMABLE)"); auto my_unique = std::make_unique(6.7); CHECK("6.7" == get_output(my_unique)); @@ -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"));