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 <compare>
7#include <functional>
8#include <iosfwd>
9#include <string>
10#include <type_traits>
11#include <utility>
12
13#include <fmt/format.h>
14#include <userver/utils/fmt_compat.hpp>
15
16#include <boost/functional/hash_fwd.hpp>
17
18#include <userver/compiler/impl/lifetime.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 does 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>
97concept Range = meta::kIsRange<T> && !meta::kIsInstantiationOf<std::basic_string, std::remove_const_t<T>>;
98
99template <typename T>
100constexpr void CheckIfAllowsLogging() {
101 static_assert(IsStrongTypedef<T>);
102
103 if constexpr (T::kOps & StrongTypedefOps::kNonLoggable) {
104 static_assert(!sizeof(T), "Trying to print a non-loggable StrongTypedef");
105 }
106}
107
108template <class To, class... Args>
109constexpr bool IsStrongToStrongConversion() noexcept {
110 static_assert(IsStrongTypedef<To>);
111
112 if constexpr (sizeof...(Args) == 1) {
113 using FromDecayed = std::decay_t<decltype((std::declval<Args>(), ...))>;
114 if constexpr (IsStrongTypedef<FromDecayed>) {
115 // Required to make `MyVariant v{MySpecialInt{10}};` compile.
116 return !std::is_same_v<FromDecayed, To> &&
117 (std::is_same_v<typename FromDecayed::UnderlyingType, typename To::UnderlyingType> ||
118 std::is_arithmetic_v<typename To::UnderlyingType>);
119 }
120 }
121
122 return false;
123}
124
125} // namespace impl::strong_typedef
126
127/// @ingroup userver_universal userver_containers
128///
129/// @brief Strong typedef for a type T.
130///
131/// Typical usage:
132/// @code
133/// using MyString = utils::StrongTypedef<class MyStringTag, std::string>;
134/// @endcode
135///
136/// Or:
137/// @code
138/// struct MyString final : utils::StrongTypedef<MyString, std::string> {
139/// using StrongTypedef::StrongTypedef;
140/// };
141/// @endcode
142///
143/// Has all the:
144/// * comparison (see "Operators" below)
145/// * hashing
146/// * streaming operators
147/// * optimizaed logging for LOG_XXX()
148///
149/// If used with container-like type also has common STL functions:
150/// * begin()
151/// * end()
152/// * cbegin()
153/// * cend()
154/// * size()
155/// * empty()
156/// * clear()
157/// * operator[]
158///
159/// Operators:
160/// You can customize the operators that are available by passing the third
161/// argument of type StrongTypedefOps. See its docs for more info.
162template <class Tag, class T, StrongTypedefOps Ops>
163class StrongTypedef : public impl::strong_typedef::StrongTypedefTag {
164 static_assert(!std::is_reference<T>::value);
165 static_assert(!std::is_pointer<T>::value);
166
167 static_assert(!std::is_reference<Tag>::value);
168 static_assert(!std::is_pointer<Tag>::value);
169
170public:
171 using UnderlyingType = T;
172 using TagType = Tag;
173 static constexpr StrongTypedefOps kOps = Ops;
174
175 StrongTypedef() = default;
176 StrongTypedef(const StrongTypedef&) = default;
177 StrongTypedef(StrongTypedef&&) noexcept = default;
178 StrongTypedef& operator=(const StrongTypedef&) = default;
179 StrongTypedef& operator=(StrongTypedef&&) noexcept = default;
180
181 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst)
182 : data_(lst)
183 {}
184
185 template <typename... Args>
186 requires std::is_constructible_v<T, Args...>
187 explicit constexpr StrongTypedef(Args&&... args) noexcept(noexcept(T(std::forward<Args>(args)...)))
188 : data_(std::forward<Args>(args)...)
189 {
190 using impl::strong_typedef::IsStrongToStrongConversion;
191 static_assert(
192 !IsStrongToStrongConversion<StrongTypedef, Args...>(),
193 "Attempt to convert one StrongTypedef to another. Use "
194 "utils::StrongCast to do that"
195 );
196 }
197
198 explicit constexpr operator const T&() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return data_; }
199 explicit constexpr operator T() && noexcept { return std::move(data_); }
200 explicit constexpr operator T&() & noexcept USERVER_IMPL_LIFETIME_BOUND { return data_; }
201
202 constexpr const T& GetUnderlying() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return data_; }
203 constexpr T GetUnderlying() && noexcept { return std::move(data_); }
204 constexpr T& GetUnderlying() & noexcept USERVER_IMPL_LIFETIME_BOUND { return data_; }
205
206 auto begin()
207 requires impl::strong_typedef::Range<T>
208 {
209 return std::begin(data_);
210 }
211
212 auto end()
213 requires impl::strong_typedef::Range<T>
214 {
215 return std::end(data_);
216 }
217
218 auto begin() const
219 requires impl::strong_typedef::Range<T>
220 {
221 return std::begin(data_);
222 }
223
224 auto end() const
225 requires impl::strong_typedef::Range<T>
226 {
227 return std::end(data_);
228 }
229
230 auto cbegin() const
231 requires impl::strong_typedef::Range<T>
232 {
233 return std::cbegin(data_);
234 }
235
236 auto cend() const
237 requires impl::strong_typedef::Range<T>
238 {
239 return std::cend(data_);
240 }
241
242 auto size() const
243 requires meta::kIsSizable<T>
244 {
245 return std::size(data_);
246 }
247
248 auto empty() const { return data_.empty(); }
249
250 auto clear() { return data_.clear(); }
251
252 template <class Arg>
253 decltype(auto) operator[](Arg&& i) {
254 return data_[std::forward<Arg>(i)];
255 }
256 template <class Arg>
257 decltype(auto) operator[](Arg&& i) const {
258 return data_[std::forward<Arg>(i)];
259 }
260
261private:
262 T data_{};
263};
264
265// Relational operators
266
267// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
268#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
269 template <
270 class T,
271 class U,
272 std::enable_if_t<
273 impl::strong_typedef::IsStrongTypedef<T> || impl::strong_typedef::IsStrongTypedef<U>,
274 int> /*Enable*/
275 = 0>
276 constexpr auto operator OPERATOR(const T& lhs, const U& rhs)
277 ->decltype(impl::strong_typedef::UnwrapIfStrongTypedef(lhs
278 ) OPERATOR impl::strong_typedef::UnwrapIfStrongTypedef(rhs)) {
279 if constexpr (impl::strong_typedef::IsStrongTypedef<T>) {
280 if constexpr (impl::strong_typedef::IsStrongTypedef<U>) {
281 impl::strong_typedef::CheckStrongCompare<T, U>();
282 return lhs.GetUnderlying() OPERATOR rhs.GetUnderlying();
283 } else {
284 impl::strong_typedef::CheckTransparentCompare<T>();
285 return lhs.GetUnderlying() OPERATOR rhs;
286 }
287 } else {
288 impl::strong_typedef::CheckTransparentCompare<U>();
289 return lhs OPERATOR rhs.GetUnderlying();
290 }
291 }
292
300
301#undef UTILS_STRONG_TYPEDEF_REL_OP
302
303/// Ostreams and Logging
304
305template <class Tag, class T, StrongTypedefOps Ops>
306std::ostream& operator<<(std::ostream& os, const StrongTypedef<Tag, T, Ops>& v) {
307 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
308 return os << v.GetUnderlying();
309}
310
311template <class Tag, class T, StrongTypedefOps Ops>
312logging::LogHelper& operator<<(logging::LogHelper& os, const StrongTypedef<Tag, T, Ops>& v) {
313 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
314 return os << v.GetUnderlying();
315}
316
317// UnderlyingValue
318template <class Tag, class T, StrongTypedefOps Ops>
319constexpr decltype(auto) UnderlyingValue(const StrongTypedef<Tag, T, Ops>& v) noexcept {
320 return v.GetUnderlying();
321}
322
323template <class Tag, class T, StrongTypedefOps Ops>
324constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v) noexcept {
325 return std::move(v).GetUnderlying();
326}
327
328constexpr bool IsStrongTypedefLoggable(StrongTypedefOps ops) { return !(ops & StrongTypedefOps::kNonLoggable); }
329
330// Serialization
331
332template <impl::strong_typedef::IsStrongTypedef T, formats::common::kIsFormatValue Value>
333T Parse(const Value& source, formats::parse::To<T>) {
334 return T{source.template As<typename T::UnderlyingType>()};
335}
336
337template <impl::strong_typedef::IsStrongTypedef T, typename Value>
338Value 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 <impl::strong_typedef::IsStrongTypedef T, typename StringBuilder>
344void 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, meta::kIsInteger T, StrongTypedefOps Ops>
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, std::floating_point T, StrongTypedefOps Ops>
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>
373constexpr Target StrongCast(const StrongTypedef<Tag, T, Ops>& src) {
374 static_assert(impl::strong_typedef::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>
384constexpr Target StrongCast(StrongTypedef<Tag, T, Ops>&& src) {
385 static_assert(impl::strong_typedef::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 <USERVER_NAMESPACE::utils::impl::strong_typedef::IsStrongTypedef T, class Char>
427struct fmt::formatter<T, Char> : fmt::formatter<typename T::UnderlyingType, Char> {
428 template <typename FormatContext>
429 auto format(const T& v, FormatContext& ctx) USERVER_FMT_CONST {
430 USERVER_NAMESPACE::utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
431 return fmt::formatter<typename T::UnderlyingType, Char>::format(v.GetUnderlying(), ctx);
432 }
433};