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