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