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>
This commit is contained in:
Gabriel Busnot
2023-02-27 15:48:23 +00:00
committed by Bobby Bruce
parent c634b23305
commit 73afee1e0d
4 changed files with 263 additions and 53 deletions

View File

@@ -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

View File

@@ -26,3 +26,4 @@
Import('*')
GTest('hash_helpers.test', 'hash_helpers.test.cc')
GTest('ostream_helpers.test', 'ostream_helpers.test.cc')

View File

@@ -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

View File

@@ -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), ]");
}