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