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