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