quill
DeferredFormatCodec.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/Codec.h"
11 #include "quill/core/DynamicFormatArgStore.h"
12 #include "quill/core/InlinedVector.h"
13 
14 #include <cstddef>
15 #include <cstdint>
16 #include <cstring>
17 #include <new>
18 #include <type_traits>
19 #include <utility>
20 
21 QUILL_BEGIN_NAMESPACE
22 
23 #if defined(_WIN32) && defined(_MSC_VER)
24  // silence msvc warning C4702: unreachable code
25  #pragma warning(push)
26  #pragma warning(disable : 4702)
27 #endif
28 
90 QUILL_BEGIN_EXPORT
91 
92 template <typename T>
94 {
95  static constexpr bool use_memcpy =
96  std::conjunction_v<std::is_trivially_copyable<T>, std::is_default_constructible<T>>;
97 
98  static size_t compute_encoded_size(detail::SizeCacheVector&, T const&) noexcept
99  {
100  if constexpr (use_memcpy)
101  {
102  return sizeof(T);
103  }
104  else
105  {
106  // If it’s misaligned, the worst-case scenario is when the pointer is off by one byte from an alignment boundary
107  return sizeof(T) + alignof(T) - 1;
108  }
109  }
110 
111  template <typename Arg>
112  static void encode(std::byte*& buffer, detail::SizeCacheVector const&, uint32_t&, Arg&& arg)
113  {
114  if constexpr (use_memcpy)
115  {
116  std::memcpy(buffer, &arg, sizeof(arg));
117  buffer += sizeof(arg);
118  }
119  else
120  {
121  auto aligned_ptr = align_pointer(buffer, alignof(T));
122 
123  if constexpr (std::is_rvalue_reference_v<Arg&&> && is_move_constructible<T>::value)
124  {
125  new (static_cast<void*>(aligned_ptr)) T(std::move(arg));
126  }
127  else
128  {
129  static_assert(is_copy_constructible<T>::value, "T is not copy-constructible!");
130  new (static_cast<void*>(aligned_ptr)) T(arg);
131  }
132 
133  buffer += sizeof(T) + alignof(T) - 1;
134  }
135  }
136 
137  static T decode_arg(std::byte*& buffer)
138  {
139  if constexpr (use_memcpy)
140  {
141  T arg;
142 
143  // Cast to void* to silence compiler warning about private members
144  std::memcpy(static_cast<void*>(&arg), buffer, sizeof(arg));
145 
146  buffer += sizeof(arg);
147  return arg;
148  }
149  else
150  {
151  static_assert(std::is_move_constructible_v<T> || std::is_copy_constructible_v<T>,
152  "T must be move or copy constructible");
153 
154  auto* aligned_ptr = align_pointer(buffer, alignof(T));
155  auto* tmp = std::launder(reinterpret_cast<T*>(aligned_ptr));
156  buffer += sizeof(T) + alignof(T) - 1;
157 
158  // Guarantee *tmp is destroyed even if the move/copy below throws. The original object
159  // was placement-new'd into the queue buffer during encode().
160  struct DestroyGuard
161  {
162  T* ptr;
163  ~DestroyGuard()
164  {
165  if constexpr (!std::is_trivially_destructible_v<T>)
166  {
167  ptr->~T();
168  }
169  }
170  } destroy_guard{tmp};
171 
172  if constexpr (std::is_move_constructible_v<T>)
173  {
174  // Move into the backend value; DestroyGuard still owns the queue-buffer object.
175  return T{std::move(*tmp)};
176  }
177  else
178  {
179  // Keep the return as an explicit copy. `return T{*tmp};` is preferred over `return *tmp;`
180  // since the latter can still prefer a deleted move constructor for copy-only types.
181  return T{*tmp};
182  }
183  }
184  }
185 
186  static void decode_and_store_arg(std::byte*& buffer, DynamicFormatArgStore* args_store)
187  {
188  QUILL_TRY { args_store->push_back(decode_arg(buffer)); }
189 #if !defined(QUILL_NO_EXCEPTIONS)
190  QUILL_CATCH_ALL()
191  {
192  // Fall back to a placeholder so the surrounding log line is still produced. Without this,
193  // a throwing decode would propagate up and discard the whole transit event.
194  static constexpr char fallback[] = "[Quill deferred decode failed]";
195  args_store->push_back(fmtquill::string_view{fallback, sizeof(fallback) - 1u});
196  }
197 #endif
198  }
199 
200 private:
201  // These trait implementations will take the friend declaration into account
202 
203  // Default constructible check
204  template <typename U, typename = void>
205  struct is_default_constructible : std::false_type
206  {
207  };
208 
209  template <typename U>
210  struct is_default_constructible<U, std::void_t<decltype(U())>> : std::true_type
211  {
212  };
213 
214  // Copy constructible check: tests if we can call U(const U&)
215  template <typename U, typename = void>
216  struct is_copy_constructible : std::false_type
217  {
218  };
219 
220  template <typename U>
221  struct is_copy_constructible<U, std::void_t<decltype(U(std::declval<U const&>()))>> : std::true_type
222  {
223  };
224 
225  // Move constructible check: tests if we can call U(U&&)
226  template <typename U, typename = void>
227  struct is_move_constructible : std::false_type
228  {
229  };
230 
231  template <typename U>
232  struct is_move_constructible<U, std::void_t<decltype(U(std::declval<U&&>()))>> : std::true_type
233  {
234  };
235 
236  static std::byte* align_pointer(void* pointer, size_t alignment) noexcept
237  {
238  return reinterpret_cast<std::byte*>((reinterpret_cast<uintptr_t>(pointer) + (alignment - 1ul)) &
239  ~(alignment - 1ul));
240  }
241 };
242 
243 QUILL_END_EXPORT
244 
245 #if defined(_WIN32) && defined(_MSC_VER)
246  #pragma warning(pop)
247 #endif
248 
249 QUILL_END_NAMESPACE
Provides serialization (codec) functionality for complex user-defined types.
Definition: DeferredFormatCodec.h:93
Similar to fmt::dynamic_arg_store but better suited to our needs e.g.
Definition: DynamicFormatArgStore.h:79
void push_back(T &&arg)
Adds an argument for later passing to a formatting function.
Definition: DynamicFormatArgStore.h:118