KASSERT  0.0.1
Karlsruhe Assertion Library
logger.hpp
Go to the documentation of this file.
1// This file is part of KAssert.
2//
3// Copyright 2021-2022 The KAssert Authors
4
5/// @file
6/// @brief Logger utility class to build error messages for failed assertins.
7
8#pragma once
9
10#include <ostream>
11#include <sstream>
12#include <type_traits>
13#include <utility>
14#include <vector>
15
16namespace kassert::internal {
17// If partially specialized template is not applicable, set value to false.
18template <typename, typename, typename = void>
19struct is_streamable_type_impl : std::false_type {};
20
21// Partially specialize template if StreamT::operator<<(ValueT) is valid.
22template <typename StreamT, typename ValueT>
24 StreamT,
25 ValueT,
26 std::void_t<decltype(std::declval<StreamT&>() << std::declval<ValueT>())>> : std::true_type {};
27
28/// @brief Determines whether a value of type \c ValueT can be streamed into an output stream of type \c StreamT.
29/// @ingroup expression-expansion
30/// @tparam StreamT An output stream overloading the \c << operator.
31/// @tparam ValueT A value type that may or may not be used with \c StreamT::operator<<.
32template <typename StreamT, typename ValueT>
33constexpr bool is_streamable_type = is_streamable_type_impl<StreamT, ValueT>::value;
34} // namespace kassert::internal
35
36namespace kassert {
37/// @brief Simple wrapper for output streams that is used to stringify values in assertions and exceptions.
38///
39/// To enable stringification for custom types, overload the \c << operator of this class.
40/// The library overloads this operator for the following STL types:
41///
42/// * \c std::vector<T>
43/// * \c std::pair<K, V>
44///
45/// @tparam StreamT The underlying streaming object (e.g., \c std::ostream or \c std::ostringstream).
46template <typename StreamT>
47class Logger {
48public:
49 /// @brief Construct the object with an underlying streaming object.
50 /// @param out The underlying streaming object.
51 explicit Logger(StreamT&& out) : _out_buffer(), _out(std::forward<StreamT>(out)) {
52 _out_buffer << std::boolalpha;
53 }
54
55 /// @brief Forward all values for which \c StreamT::operator<< is defined to the underlying streaming object.
56 /// @param value Value to be stringified.
57 /// @tparam ValueT Type of the value to be stringified.
58 template <typename ValueT, std::enable_if_t<internal::is_streamable_type<std::ostream, ValueT>, int> = 0>
59 Logger<StreamT>& operator<<(ValueT&& value) {
60 // we buffer logged values and only flush when the destructor is called or the buffer is flushed manually
61 // this prevents interleaving of the outputs of multiple processes (e.g. MPI ranks)
62 _out_buffer << std::forward<ValueT>(value);
63 return *this;
64 }
65
66 /// @brief Get the underlying streaming object.
67 /// Flushes all buffered logs to the underlying stream before returning a reference to the stream.
68 /// @return The underlying streaming object.
69 StreamT&& stream() {
70 flush();
71 return std::forward<StreamT>(_out);
72 }
73
74 /// @brief Flushes all buffered logs to the underlying stream.
75 void flush() {
76 _out << _out_buffer.str() << std::flush;
77 _out_buffer.str(std::string{});
78 }
79
80 /// @brief Destructor of the logger stream, which flushes all buffered logs to the underlying stream upon
81 /// destruction.
82 ~Logger() {
83 flush();
84 }
85
86private:
87 std::stringstream _out_buffer; ///> @brief The output buffer.
88 StreamT&& _out; ///> @brief The underlying streaming object.
89};
90} // namespace kassert
91
92namespace kassert::internal {
93/// @addtogroup expression-expansion
94/// @{
95
96/// @brief Stringify a value using the given assertion logger. If the value cannot be streamed into the logger, print
97/// \c <?> instead.
98/// @tparam StreamT The underlying streaming object of the assertion logger.
99/// @tparam ValueT The type of the value to be stringified.
100/// @param out The assertion logger.
101/// @param value The value to be stringified.
102template <typename StreamT, typename ValueT>
103void stringify_value(Logger<StreamT>& out, ValueT const& value) {
104 if constexpr (is_streamable_type<Logger<StreamT>, ValueT>) {
105 out << value;
106 } else {
107 out << "<?>";
108 }
109}
110
111/// @brief Logger writing all output to a \c std::ostream. This specialization is used to generate the KASSERT error
112/// messages.
113using OStreamLogger = Logger<std::ostream&>;
114
115/// @brief Logger writing all output to a rvalue \c std::ostringstream. This specialization is used to generate the
116/// custom error message for THROWING_KASSERT exceptions.
117using RrefOStringstreamLogger = Logger<std::ostringstream&&>;
118
119/// @}
120} // namespace kassert::internal
121
122namespace kassert {
123
124/// @brief Stringification of `std::vector<T>` in assertions.
125///
126/// Outputs a `std::vector<T>` in the following format, where `element i` are the stringified elements of the
127/// vector: `[element 1, element 2, ...]`
128///
129/// @tparam StreamT The underlying output stream of the Logger.
130/// @tparam ValueT The type of the elements contained in the vector.
131/// @tparam AllocatorT The allocator of the vector.
132/// @param logger The assertion logger.
133/// @param container The vector to be stringified.
134/// @return The stringified vector as described above.
135template <typename StreamT, typename ValueT, typename AllocatorT>
136Logger<StreamT>& operator<<(Logger<StreamT>& logger, std::vector<ValueT, AllocatorT> const& container) {
137 logger << "[";
138 bool first = true;
139 for (auto const& element: container) {
140 if (!first) {
141 logger << ", ";
142 }
143 logger << element;
144 first = false;
145 }
146 return logger << "]";
147}
148
149/// @brief Stringification of `std::pair<K, V>` in assertions.
150///
151/// Outputs a `std::pair<K, V>` in the following format, where `first` and `second` are the stringified
152/// components of the pair: `(first, second)`.
153///
154/// @tparam StreamT The underlying output stream of the Logger.
155/// @tparam Key Type of the first component of the pair.
156/// @tparam Value Type of the second component of the pair.
157/// @param logger The assertion logger.
158/// @param pair The pair to be stringified.
159/// @return The stringification of the pair as described above.
160template <typename StreamT, typename Key, typename Value>
161Logger<StreamT>& operator<<(Logger<StreamT>& logger, std::pair<Key, Value> const& pair) {
162 return logger << "(" << pair.first << ", " << pair.second << ")";
163}
164} // namespace kassert