userver: userver/utils/strong_typedef.hpp Source File
Loading...
Searching...
No Matches
strong_typedef.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/strong_typedef.hpp
4/// @brief @copybrief utils::StrongTypedef
5
6#include <functional>
7#include <iosfwd>
8#include <type_traits>
9#include <utility>
10
11#include <fmt/format.h>
12#include <userver/utils/fmt_compat.hpp>
13
14#include <boost/functional/hash_fwd.hpp>
15
16#include <userver/formats/common/meta.hpp>
17#include <userver/utils/meta.hpp>
18#include <userver/utils/underlying_value.hpp>
19#include <userver/utils/void_t.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace logging {
24class LogHelper; // Forward declaration
25}
26
27namespace utils {
28
29enum class StrongTypedefOps {
30 kNoCompare = 0, /// Forbid all comparisons for StrongTypedef
31
32 kCompareStrong = 1, /// Allow comparing two StrongTypedef<Tag, T>
33 kCompareTransparentOnly = 2, /// Allow comparing StrongTypedef<Tag, T> and T
34 kCompareTransparent = 3, /// Allow both of the above
35
36 kNonLoggable = 4, /// Forbid logging and serializing for StrongTypedef
37};
38
39constexpr bool operator&(StrongTypedefOps op, StrongTypedefOps mask) noexcept {
40 return utils::UnderlyingValue(op) & utils::UnderlyingValue(mask);
41}
42
43constexpr auto operator|(StrongTypedefOps op1, StrongTypedefOps op2) noexcept {
44 return StrongTypedefOps{utils::UnderlyingValue(op1) |
45 utils::UnderlyingValue(op2)};
46}
47
48/// @ingroup userver_universal userver_containers
49///
50/// @brief Strong typedef for a type T.
51///
52/// Typical usage:
53/// @code
54/// using MyString = utils::StrongTypedef<class MyStringTag, std::string>;
55/// @endcode
56///
57/// Or:
58/// @code
59/// struct MyString final : utils::StrongTypedef<MyString, std::string> {
60/// using StrongTypedef::StrongTypedef;
61/// };
62/// @endcode
63///
64/// Has all the:
65/// * comparison (see "Operators" below)
66/// * hashing
67/// * streaming operators
68/// * optimizaed logging for LOG_XXX()
69///
70/// If used with container-like type also has common STL functions:
71/// * begin()
72/// * end()
73/// * cbegin()
74/// * cend()
75/// * size()
76/// * empty()
77/// * clear()
78/// * operator[]
79///
80/// Operators:
81/// You can customize the operators that are available by passing the third
82/// argument of type StrongTypedefOps. See its docs for more info.
83template <class Tag, class T,
85 class /*Enable*/ = void>
86class StrongTypedef; // Forward declaration
87
88// Helpers
89namespace impl::strong_typedef {
90
91template <class T, class /*Enable*/ = void_t<>>
92struct InitializerListImpl {
93 struct DoNotMatch;
94 using type = DoNotMatch;
95};
96
97template <class T>
98struct InitializerListImpl<T, void_t<typename T::value_type>> {
99 using type = std::initializer_list<typename T::value_type>;
100};
101
102// We have to invent this alias to avoid hard error in StrongTypedef<Tag, T> for
103// types T that do not have T::value_type.
104template <class T>
105using InitializerList = typename InitializerListImpl<T>::type;
106
107template <class Tag, class T, StrongTypedefOps Ops, class Other>
111 bool>;
112
113struct StrongTypedefTag {};
114
115template <typename T>
116using IsStrongTypedef =
117 std::conjunction<std::is_base_of<StrongTypedefTag, T>,
118 std::is_convertible<T&, StrongTypedefTag&>>;
119
120// For 'std::string', begin-end methods are not forwarded, because otherwise
121// it might get serialized as an array.
122template <typename T, typename Void>
124 std::is_void_v<Void> && meta::kIsRange<T> &&
126
127template <typename T, typename Void>
128using EnableIfSizeable =
130
131template <typename T>
132constexpr void CheckIfAllowsLogging() {
133 static_assert(IsStrongTypedef<T>::value);
134
135 if constexpr (T::kOps & StrongTypedefOps::kNonLoggable) {
136 static_assert(!sizeof(T), "Trying to print a non-loggable StrongTypedef");
137 }
138}
139
140template <class To, class... Args>
141constexpr bool IsStrongToStrongConversion() noexcept {
142 static_assert(IsStrongTypedef<To>::value);
143
144 if constexpr (sizeof...(Args) == 1) {
145 using FromDecayed = std::decay_t<decltype((std::declval<Args>(), ...))>;
146 if constexpr (IsStrongTypedef<FromDecayed>::value) {
147 // Required to make `MyVariant v{MySpecialInt{10}};` compile.
148 return !std::is_same_v<FromDecayed, To> &&
149 (std::is_same_v<typename FromDecayed::UnderlyingType,
150 typename To::UnderlyingType> ||
151 std::is_arithmetic_v<typename To::UnderlyingType>);
152 }
153 }
154
155 return false;
156}
157
158} // namespace impl::strong_typedef
159
160using impl::strong_typedef::IsStrongTypedef;
161
162template <class Tag, class T, StrongTypedefOps Ops, class /*Enable*/>
163class StrongTypedef : public impl::strong_typedef::StrongTypedefTag {
164 static_assert(!std::is_reference<T>::value);
165 static_assert(!std::is_pointer<T>::value);
166
167 static_assert(!std::is_reference<Tag>::value);
168 static_assert(!std::is_pointer<Tag>::value);
169
170 public:
171 using UnderlyingType = T;
172 using TagType = Tag;
173 static constexpr StrongTypedefOps kOps = Ops;
174
175 StrongTypedef() = default;
176 StrongTypedef(const StrongTypedef&) = default;
177 StrongTypedef(StrongTypedef&&) noexcept = default;
178 StrongTypedef& operator=(const StrongTypedef&) = default;
179 StrongTypedef& operator=(StrongTypedef&&) noexcept = default;
180
181 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst)
182 : data_(lst) {}
183
184 template <typename... Args,
185 typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
186 explicit constexpr StrongTypedef(Args&&... args) noexcept(
187 noexcept(T(std::forward<Args>(args)...)))
188 : data_(std::forward<Args>(args)...) {
189 using impl::strong_typedef::IsStrongToStrongConversion;
190 static_assert(!IsStrongToStrongConversion<StrongTypedef, Args...>(),
191 "Attempt to convert one StrongTypedef to another. Use "
192 "utils::StrongCast to do that");
193 }
194
195 explicit constexpr operator const T&() const& noexcept { return data_; }
196 explicit constexpr operator T() && noexcept { return std::move(data_); }
197 explicit constexpr operator T&() & noexcept { return data_; }
198
199 constexpr const T& GetUnderlying() const& noexcept { return data_; }
200 constexpr T GetUnderlying() && noexcept { return std::move(data_); }
201 constexpr T& GetUnderlying() & noexcept { return data_; }
202
203 template <typename Void = void,
204 typename = impl::strong_typedef::EnableIfRange<T, Void>>
205 auto begin() {
206 return std::begin(data_);
207 }
208
209 template <typename Void = void,
210 typename = impl::strong_typedef::EnableIfRange<T, Void>>
211 auto end() {
212 return std::end(data_);
213 }
214
215 template <typename Void = void,
216 typename = impl::strong_typedef::EnableIfRange<const T, Void>>
217 auto begin() const {
218 return std::begin(data_);
219 }
220
221 template <typename Void = void,
222 typename = impl::strong_typedef::EnableIfRange<const T, Void>>
223 auto end() const {
224 return std::end(data_);
225 }
226
227 template <typename Void = void,
228 typename = impl::strong_typedef::EnableIfRange<const T, Void>>
229 auto cbegin() const {
230 return std::cbegin(data_);
231 }
232
233 template <typename Void = void,
234 typename = impl::strong_typedef::EnableIfRange<const T, Void>>
235 auto cend() const {
236 return std::cend(data_);
237 }
238
239 template <typename Void = void,
240 typename = impl::strong_typedef::EnableIfSizeable<const T, Void>>
241 auto size() const {
242 return std::size(data_);
243 }
244
245 auto empty() const { return data_.empty(); }
246
247 auto clear() { return data_.clear(); }
248
249 template <class Arg>
250 decltype(auto) operator[](Arg&& i) {
251 return data_[std::forward<Arg>(i)];
252 }
253 template <class Arg>
254 decltype(auto) operator[](Arg&& i) const {
255 return data_[std::forward<Arg>(i)];
256 }
257
258 private:
259 T data_{};
260};
261
262// Relational operators
263
264// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
265#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
266 template <class Tag1, class T1, StrongTypedefOps Ops1, class Tag2, class T2,
267 StrongTypedefOps Ops2>
268 constexpr bool operator OPERATOR(const StrongTypedef<Tag1, T1, Ops1>&,
269 const StrongTypedef<Tag2, T2, Ops2>&) {
270 static_assert(!sizeof(T1), "Comparing those StrongTypedefs is forbidden");
271 return false;
272 }
273
274 template <class Tag, class T, StrongTypedefOps Ops>
275 constexpr std::enable_if_t<Ops & StrongTypedefOps::kCompareStrong, bool>
276 operator OPERATOR(const StrongTypedef<Tag, T, Ops>& lhs,
277 const StrongTypedef<Tag, T, Ops>& rhs) {
278 return lhs.GetUnderlying() OPERATOR rhs.GetUnderlying();
279 }
280
281 template <class Tag, class T, StrongTypedefOps Ops, class Other>
282 constexpr impl::strong_typedef::EnableTransparentCompare<Tag, T, Ops, Other>
283 operator OPERATOR(const StrongTypedef<Tag, T, Ops>& lhs, const Other& rhs) {
284 return lhs.GetUnderlying() OPERATOR rhs;
285 }
286
287 template <class Tag, class T, StrongTypedefOps Ops, class Other>
288 constexpr impl::strong_typedef::EnableTransparentCompare<Tag, T, Ops, Other>
289 operator OPERATOR(const Other& lhs, const StrongTypedef<Tag, T, Ops>& rhs) {
290 return lhs OPERATOR rhs.GetUnderlying();
291 }
292
299
300#undef UTILS_STRONG_TYPEDEF_REL_OP
301
302/// Ostreams and Logging
303
304template <class Tag, class T, StrongTypedefOps Ops>
305std::ostream& operator<<(std::ostream& os,
306 const StrongTypedef<Tag, T, Ops>& v) {
307 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
308 return os << v.GetUnderlying();
309}
310
311template <class Tag, class T, StrongTypedefOps Ops>
312logging::LogHelper& operator<<(logging::LogHelper& os,
313 const StrongTypedef<Tag, T, Ops>& v) {
314 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
315 return os << v.GetUnderlying();
316}
317
318// UnderlyingValue
319template <class Tag, class T, StrongTypedefOps Ops>
320constexpr decltype(auto) UnderlyingValue(
321 const StrongTypedef<Tag, T, Ops>& v) noexcept {
322 return v.GetUnderlying();
323}
324
325template <class Tag, class T, StrongTypedefOps Ops>
326constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v) noexcept {
327 return std::move(v).GetUnderlying();
328}
329
330constexpr bool IsStrongTypedefLoggable(StrongTypedefOps Ops) {
331 return !(Ops & StrongTypedefOps::kNonLoggable);
332}
333
334// Serialization
335
336template <typename T, typename Value>
337std::enable_if_t<formats::common::kIsFormatValue<Value> && IsStrongTypedef<T>{},
338 T>
339Parse(const Value& source, formats::parse::To<T>) {
340 return T{source.template As<typename T::UnderlyingType>()};
341}
342
343template <typename T, typename Value>
344std::enable_if_t<IsStrongTypedef<T>{}, Value> Serialize(
345 const T& object, formats::serialize::To<Value>) {
346 impl::strong_typedef::CheckIfAllowsLogging<T>();
347 return typename Value::Builder(object.GetUnderlying()).ExtractValue();
348}
349
350template <typename T, typename StringBuilder>
351std::enable_if_t<IsStrongTypedef<T>{}> WriteToStream(const T& object,
352 StringBuilder& sw) {
353 impl::strong_typedef::CheckIfAllowsLogging<T>();
354 WriteToStream(object.GetUnderlying(), sw);
355}
356
357template <typename Tag, StrongTypedefOps Ops>
358std::string ToString(const StrongTypedef<Tag, std::string, Ops>& object) {
359 impl::strong_typedef::CheckIfAllowsLogging<
360 StrongTypedef<Tag, std::string, Ops>>();
361 return object.GetUnderlying();
362}
363
364template <typename Tag, typename T, StrongTypedefOps Ops,
365 std::enable_if_t<meta::kIsInteger<T>, bool> = true>
366std::string ToString(const StrongTypedef<Tag, T, Ops>& object) {
367 impl::strong_typedef::CheckIfAllowsLogging<
368 StrongTypedef<Tag, std::string, Ops>>();
369 return std::to_string(object.GetUnderlying());
370}
371
372template <typename Tag, typename T, StrongTypedefOps Ops,
373 std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
374std::string ToString(const StrongTypedef<Tag, T, Ops>& object) {
375 impl::strong_typedef::CheckIfAllowsLogging<
376 StrongTypedef<Tag, std::string, Ops>>();
377 return fmt::format("{}", object.GetUnderlying());
378}
379
380// Explicit casting
381
382/// Explicitly cast from one strong typedef to another, to replace constructions
383/// `SomeStrongTydef{utils::UnderlyingValue(another_strong_val)}` with
384/// `utils::StrongCast<SomeStrongTydef>(another_strong_val)`
385template <typename Target, typename Tag, typename T, StrongTypedefOps Ops,
386 typename Enable>
387constexpr Target StrongCast(const StrongTypedef<Tag, T, Ops, Enable>& src) {
388 static_assert(IsStrongTypedef<Target>{},
389 "Expected strong typedef as target type");
390 static_assert(std::is_convertible_v<T, typename Target::UnderlyingType>,
391 "Source strong typedef underlying type must be convertible to "
392 "target's underlying type");
393 return Target{src.GetUnderlying()};
394}
395
396template <typename Target, typename Tag, typename T, StrongTypedefOps Ops,
397 typename Enable>
398constexpr Target StrongCast(StrongTypedef<Tag, T, Ops, Enable>&& src) {
399 static_assert(IsStrongTypedef<Target>{},
400 "Expected strong typedef as target type");
401 static_assert(std::is_convertible_v<T, typename Target::UnderlyingType>,
402 "Source strong typedef underlying type must be convertible to "
403 "target's underlying type");
404 return Target{std::move(src).GetUnderlying()};
405}
406
407template <class Tag, class T, StrongTypedefOps Ops>
408std::size_t hash_value(const StrongTypedef<Tag, T, Ops>& v) {
409 return boost::hash<T>{}(v.GetUnderlying());
410}
411
412/// A StrongTypedef for data that MUST NOT be logged or outputted in some other
413/// way. Also prevents the data from appearing in backtrace prints of debugger.
414///
415/// @snippet storages/secdist/secdist_test.cpp UserPasswords
416template <class Tag, class T>
417using NonLoggable = StrongTypedef<
418 Tag, T, StrongTypedefOps::kCompareStrong | StrongTypedefOps::kNonLoggable>;
419
420} // namespace utils
421
422USERVER_NAMESPACE_END
423
424// std::hash support
425template <class Tag, class T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops>
426struct std::hash<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops>>
427 : std::hash<T> {
428 std::size_t operator()(
429 const USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops>& v) const
430 noexcept(noexcept(
431 std::declval<const std::hash<T>>()(std::declval<const T&>()))) {
432 return std::hash<T>::operator()(v.GetUnderlying());
433 }
434};
435
436// fmt::format support
437template <class T, class Char>
438struct fmt::formatter<
439 T, Char, std::enable_if_t<USERVER_NAMESPACE::utils::IsStrongTypedef<T>{}>>
440 : fmt::formatter<typename T::UnderlyingType, Char> {
441 template <typename FormatContext>
442 auto format(const T& v, FormatContext& ctx) USERVER_FMT_CONST {
443 USERVER_NAMESPACE::utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
444 return fmt::formatter<typename T::UnderlyingType, Char>::format(
445 v.GetUnderlying(), ctx);
446 }
447};