base: Provide stl_helpers::operator<< for more types

This operator can be safely brought in scope when needed with "using
stl_helpers::operator<<".

In order to provide a specialization for operator<< with
stl_helpers-enabled types without loosing the hability to use it with
other types, a dual-dispatch mechanism is used. The only entry point
in the system is through a primary dispatch function that won't
resolve for non-helped types. Then, recursive calls go through the
secondary dispatch interface that sort between helped and non-helped
types. Helped typed will enter the system back through the primary
dispatch interface while other types will look for operator<< through
regular lookup, especially ADL.

Change-Id: I1609dd6e85e25764f393458d736ec228e025da32
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/67666
Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br>
Tested-by: kokoro <noreply+kokoro@google.com>
Maintainer: Bobby Bruce <bbruce@ucdavis.edu>
diff --git a/src/base/stl_helpers.hh b/src/base/stl_helpers.hh
index 1d19f56..b70eea9 100644
--- a/src/base/stl_helpers.hh
+++ b/src/base/stl_helpers.hh
@@ -26,59 +26,10 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef __BASE_STL_HELPERS_HH__
-#define __BASE_STL_HELPERS_HH__
+#ifndef BASE_STL_HELPERS_HH
+#define BASE_STL_HELPERS_HH
 
-#include <algorithm>
-#include <iostream>
-#include <numeric>
-#include <type_traits>
-#include <vector>
-
-#include "base/compiler.hh"
 #include "base/stl_helpers/hash_helpers.hh"
+#include "base/stl_helpers/ostream_helpers.hh"
 
-namespace gem5
-{
-
-namespace stl_helpers
-{
-
-template <typename T, typename Enabled=void>
-struct IsHelpedContainer : public std::false_type {};
-
-template <typename ...Types>
-struct IsHelpedContainer<std::vector<Types...>> : public std::true_type {};
-
-template <typename ...Types>
-constexpr bool IsHelpedContainerV = IsHelpedContainer<Types...>::value;
-
-/**
- * Write out all elements in an stl container as a space separated
- * list enclosed in square brackets
- *
- * @ingroup api_base_utils
- */
-
-template <typename T>
-std::enable_if_t<IsHelpedContainerV<T>, std::ostream &>
-operator<<(std::ostream& out, const T &t)
-{
-    out << "[ ";
-    bool first = true;
-    auto printer = [&first, &out](const auto &elem) {
-        if (first)
-            out << elem;
-        else
-            out << " " << elem;
-    };
-    std::for_each(t.begin(), t.end(), printer);
-    out << " ]";
-    out << std::flush;
-    return out;
-}
-
-} // namespace stl_helpers
-} // namespace gem5
-
-#endif // __BASE_STL_HELPERS_HH__
+#endif // BASE_STL_HELPERS_HH
diff --git a/src/base/stl_helpers/SConscript b/src/base/stl_helpers/SConscript
index 1143dc2..7328cf0 100644
--- a/src/base/stl_helpers/SConscript
+++ b/src/base/stl_helpers/SConscript
@@ -26,3 +26,4 @@
 Import('*')
 
 GTest('hash_helpers.test', 'hash_helpers.test.cc')
+GTest('ostream_helpers.test', 'ostream_helpers.test.cc')
diff --git a/src/base/stl_helpers/ostream_helpers.hh b/src/base/stl_helpers/ostream_helpers.hh
new file mode 100644
index 0000000..a1c92e1
--- /dev/null
+++ b/src/base/stl_helpers/ostream_helpers.hh
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2023 Arteris, Inc. and its applicable licensors and
+ * affiliates.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BASE_STL_HELPERS_OSTREAM_HELPERS_HH
+#define BASE_STL_HELPERS_OSTREAM_HELPERS_HH
+
+#include <iostream>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "base/type_traits.hh"
+#include "magic_enum/magic_enum.hh"
+
+namespace gem5::stl_helpers
+{
+
+namespace opExtract_impl
+{
+
+/*
+ * In order to provide a specialization for operator<< with stl_helpers-enabled
+ * types
+ * without loosing the hability to use it with other types, a dual-dispatch
+ * mechanism is used. The only entry point in the system is through a primary
+ * dispatch function that won't resolve for non-helped types. Then, recursive
+ * calls go through the secondary dispatch interface that sort between helped
+ * and non-helped types. Helped typed will enter the system back through the
+ * primary dispatch interface while other types will look for operator<<
+ * through regular lookup, especially ADL.
+ */
+
+template<typename T>
+std::ostream&
+opExtractSecDisp(std::ostream& os, const T& v);
+
+template <typename E>
+std::enable_if_t<std::is_enum_v<E>,
+std::ostream&>
+opExtractPrimDisp(std::ostream& os, const E& e)
+{
+    return os << magic_enum::enum_name(e);
+}
+
+template <typename... T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, const std::tuple<T...>& p)
+{
+    std::apply([&](auto&&... e) {
+        std::size_t n{0};
+        os << '(';
+        ((opExtractSecDisp(os, e) << (++n != sizeof...(T) ? ", " : "")), ...);
+        os << ')';
+    }, p);
+    return os;
+}
+
+template <typename T, typename U>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, const std::pair<T, U>& p)
+{
+    return opExtractPrimDisp(os, std::tie(p.first, p.second));
+}
+
+template <typename T>
+std::enable_if_t<is_iterable_v<T>, std::ostream&>
+opExtractPrimDisp(std::ostream& os, const T& v)
+{
+    os << "[ ";
+    for (auto& e: v) {
+        opExtractSecDisp(os, e) << ", ";
+    }
+    return os << ']';
+}
+
+template <typename T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, const std::optional<T>& o)
+{
+    if (o) {
+        return opExtractSecDisp(os, *o);
+    } else {
+        return os << '-';
+    }
+}
+
+template <typename T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, T* p);
+
+template <typename T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, const std::shared_ptr<T>& p)
+{
+    return opExtractPrimDisp(os, p.get());
+}
+
+template <typename T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, const std::unique_ptr<T>& p)
+{
+    return opExtractPrimDisp(os, p.get());
+}
+
+template <typename, typename = void>
+constexpr bool isOpExtractNativelySupported = false;
+
+template <typename T>
+constexpr bool isOpExtractNativelySupported<T,
+    std::void_t<decltype(
+        std::declval<std::ostream&>() << std::declval<T>())>> = true;
+
+template <typename, typename = void>
+constexpr bool isOpExtractHelped = false;
+
+template <typename T>
+constexpr bool isOpExtractHelped<T,
+    std::void_t<decltype(
+        opExtractPrimDisp(std::declval<std::ostream&>(),
+                          std::declval<T>()))>>
+    = true;
+
+template <typename T>
+constexpr bool needsDispatch =
+    isOpExtractHelped<T> && !isOpExtractNativelySupported<T>;
+
+template <typename T>
+std::ostream&
+opExtractPrimDisp(std::ostream& os, T* p)
+{
+    if (!p) {
+        return os << "nullptr";
+    }
+    if constexpr (isOpExtractHelped<T> || isOpExtractNativelySupported<T>) {
+        os << '(' << p << ": ";
+        opExtractSecDisp(os, *p);
+        return os << ')';
+    } else {
+        return os << p;
+    }
+}
+
+template<typename T>
+std::ostream&
+opExtractSecDisp(std::ostream& os, const T& v)
+{
+    if constexpr (needsDispatch<T>) {
+        return opExtractPrimDisp(os, v);
+    } else {
+        return os << v;
+    }
+}
+
+} // namespace opExtract_impl
+
+// Add "using stl_helpers::operator<<" in the scope where you want to use it.
+template<typename T>
+std::enable_if_t<opExtract_impl::needsDispatch<T>, std::ostream&>
+operator<<(std::ostream& os, const T& v)
+{
+    return opExtract_impl::opExtractPrimDisp(os, v);
+}
+
+} // namespace gem5::stl_helpers
+
+#endif // BASE_STL_HELPERS_OSTREAM_HELPERS_HH
diff --git a/src/base/stl_helpers/ostream_helpers.test.cc b/src/base/stl_helpers/ostream_helpers.test.cc
new file mode 100644
index 0000000..19a1ece
--- /dev/null
+++ b/src/base/stl_helpers/ostream_helpers.test.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023 Arteris, Inc. and its applicable licensors and
+ * affiliates.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer; redistributions in binary
+ * form must reproduce the above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation and/or other materials
+ * provided with the distribution; neither the name of the copyright holders
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gtest/gtest.h>
+
+#include <map>
+#include <sstream>
+#include <string_view>
+#include <vector>
+
+#include "base/stl_helpers/ostream_helpers.hh"
+
+using gem5::stl_helpers::operator<<;
+
+TEST(OstreamHelpers, pair) {
+    auto p = std::make_pair(1, 2);
+    std::ostringstream os;
+    os << p;
+    EXPECT_EQ(os.str(), "(1, 2)");
+}
+
+TEST(OstreamHelpers, tuple) {
+    auto t = std::make_tuple(true,
+        std::make_pair("Hello", std::string_view("World")), '!');
+    std::ostringstream os;
+    os << t;
+    EXPECT_EQ(os.str(), "(1, (Hello, World), !)");
+}
+
+TEST(OstreamHelpers, vector) {
+    auto v = std::vector<const char*>{"abc", "defg", "hijklm", "\n"};
+    std::ostringstream os;
+    os << v;
+    EXPECT_EQ(os.str(), "[ abc, defg, hijklm, \n, ]");
+}
+
+TEST(OstreamHelpers, map) {
+    auto m = std::map<char, int>{{'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}};
+    std::ostringstream os;
+    os << m;
+    EXPECT_EQ(os.str(), "[ (a, 0), (b, 1), (c, 2), (d, 3), ]");
+}