12#include <fmt/format.h>
13#include <userver/utils/fmt_compat.hpp>
15#include <boost/functional/hash_fwd.hpp>
17#include <userver/compiler/impl/lifetime.hpp>
18#include <userver/compiler/impl/three_way_comparison.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>
28std::string PrintToString(
const T& value);
32USERVER_NAMESPACE_BEGIN
40constexpr bool operator&(StrongTypedefOps op, StrongTypedefOps mask)
noexcept {
44constexpr auto operator|(StrongTypedefOps op1, StrongTypedefOps op2)
noexcept {
49namespace impl::strong_typedef {
51template <
class T,
class = void_t<>>
52struct InitializerListImpl {
54 using type = DoNotMatch;
58struct InitializerListImpl<T, void_t<
typename T::value_type>> {
59 using type = std::initializer_list<
typename T::value_type>;
65using InitializerList =
typename InitializerListImpl<T>::type;
67template <
class T,
class U>
68constexpr void CheckStrongCompare() {
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"
77template <
class Typedef>
78constexpr void CheckTransparentCompare() {
80 Typedef::kOps & StrongTypedefOps::kCompareTransparentOnly,
81 "Comparing this StrongTypedef to a raw value is forbidden"
86const auto& UnwrapIfStrongTypedef(
const T& value) {
87 if constexpr (IsStrongTypedef<T>{}) {
88 return value.GetUnderlying();
96template <
typename T,
typename Void>
97using EnableIfRange = std::enable_if_t<
98 std::is_void_v<Void> && meta::kIsRange<T> && !meta::kIsInstantiationOf<std::basic_string, std::remove_const_t<T>>>;
100template <
typename T,
typename Void>
101using EnableIfSizeable = std::enable_if_t<std::is_void_v<Void> && meta::kIsSizable<T>>;
104constexpr void CheckIfAllowsLogging() {
105 static_assert(IsStrongTypedef<T>::value);
107 if constexpr (T::kOps & StrongTypedefOps::kNonLoggable) {
108 static_assert(!
sizeof(T),
"Trying to print a non-loggable StrongTypedef");
112template <
class To,
class... Args>
113constexpr bool IsStrongToStrongConversion()
noexcept {
114 static_assert(IsStrongTypedef<To>::value);
116 if constexpr (
sizeof...(Args) == 1) {
117 using FromDecayed = std::decay_t<
decltype((std::declval<Args>(), ...))>;
118 if constexpr (IsStrongTypedef<FromDecayed>::value) {
120 return !std::is_same_v<FromDecayed, To> &&
121 (std::is_same_v<
typename FromDecayed::UnderlyingType,
typename To::UnderlyingType> ||
122 std::is_arithmetic_v<
typename To::UnderlyingType>);
166template <
class Tag,
class T, StrongTypedefOps Ops,
class >
167class StrongTypedef :
public impl::strong_typedef::StrongTypedefTag {
168 static_assert(!std::is_reference<T>::value);
169 static_assert(!std::is_pointer<T>::value);
171 static_assert(!std::is_reference<Tag>::value);
172 static_assert(!std::is_pointer<Tag>::value);
175 using UnderlyingType = T;
177 static constexpr StrongTypedefOps kOps = Ops;
179 StrongTypedef() =
default;
180 StrongTypedef(
const StrongTypedef&) =
default;
181 StrongTypedef(StrongTypedef&&)
noexcept =
default;
182 StrongTypedef& operator=(
const StrongTypedef&) =
default;
183 StrongTypedef& operator=(StrongTypedef&&)
noexcept =
default;
185 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst)
189 template <
typename... Args,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
190 explicit constexpr StrongTypedef(Args&&... args)
noexcept(
noexcept(T(std::forward<Args>(args)...)))
191 : data_(std::forward<Args>(args)...)
193 using impl::strong_typedef::IsStrongToStrongConversion;
195 !IsStrongToStrongConversion<StrongTypedef, Args...>(),
196 "Attempt to convert one StrongTypedef to another. Use "
197 "utils::StrongCast to do that"
201 explicit constexpr operator
const T&()
const&
noexcept USERVER_IMPL_LIFETIME_BOUND {
return data_; }
202 explicit constexpr operator T() &&
noexcept {
return std::move(data_); }
203 explicit constexpr operator T&() &
noexcept USERVER_IMPL_LIFETIME_BOUND {
return data_; }
205 constexpr const T& GetUnderlying()
const&
noexcept USERVER_IMPL_LIFETIME_BOUND {
return data_; }
206 constexpr T GetUnderlying() &&
noexcept {
return std::move(data_); }
207 constexpr T& GetUnderlying() &
noexcept USERVER_IMPL_LIFETIME_BOUND {
return data_; }
209 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
211 return std::begin(data_);
214 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
216 return std::end(data_);
219 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
221 return std::begin(data_);
224 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
226 return std::end(data_);
229 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
230 auto cbegin()
const {
231 return std::cbegin(data_);
234 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
236 return std::cend(data_);
239 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfSizeable<
const T, Void>>
241 return std::size(data_);
244 auto empty()
const {
return data_.empty(); }
246 auto clear() {
return data_.clear(); }
249 decltype(
auto) operator[](Arg&& i) {
250 return data_[std::forward<Arg>(i)];
253 decltype(
auto) operator[](Arg&& i)
const {
254 return data_[std::forward<Arg>(i)];
264#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
269 impl::strong_typedef::IsStrongTypedef<T>{} || impl::strong_typedef::IsStrongTypedef<U>{},
272 constexpr auto operator
OPERATOR(const T& lhs, const U& rhs)
273 ->decltype(impl::strong_typedef::UnwrapIfStrongTypedef(lhs
274 ) OPERATOR impl::strong_typedef::UnwrapIfStrongTypedef(rhs)) {
275 if constexpr (impl::strong_typedef::IsStrongTypedef<T>{}) {
276 if constexpr (impl::strong_typedef::IsStrongTypedef<U>{}) {
277 impl::strong_typedef::CheckStrongCompare<T, U>();
278 return lhs.GetUnderlying() OPERATOR rhs.GetUnderlying();
280 impl::strong_typedef::CheckTransparentCompare<T>();
281 return lhs.GetUnderlying() OPERATOR rhs;
284 impl::strong_typedef::CheckTransparentCompare<U>();
285 return lhs OPERATOR rhs.GetUnderlying();
296#ifdef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
300#undef UTILS_STRONG_TYPEDEF_REL_OP
304template <
class Tag,
class T, StrongTypedefOps Ops>
305std::ostream& operator<<(std::ostream& os,
const StrongTypedef<Tag, T, Ops>& v) {
306 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
307 return os << v.GetUnderlying();
310template <
class Tag,
class T, StrongTypedefOps Ops>
311logging::LogHelper& operator<<(logging::LogHelper& os,
const StrongTypedef<Tag, T, Ops>& v) {
312 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
313 return os << v.GetUnderlying();
317template <
class Tag,
class T, StrongTypedefOps Ops>
318constexpr decltype(
auto) UnderlyingValue(
const StrongTypedef<Tag, T, Ops>& v)
noexcept {
319 return v.GetUnderlying();
322template <
class Tag,
class T, StrongTypedefOps Ops>
323constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v)
noexcept {
324 return std::move(v).GetUnderlying();
327constexpr bool IsStrongTypedefLoggable(StrongTypedefOps ops) {
return !(ops & StrongTypedefOps::kNonLoggable); }
331template <
typename T,
typename Value>
332std::enable_if_t<formats::
common::kIsFormatValue<Value> && IsStrongTypedef<T>{}, T>
333Parse(
const Value& source, formats::
parse::To<T>) {
334 return T{source.
template As<
typename T::UnderlyingType>()};
337template <
typename T,
typename Value>
338std::enable_if_t<IsStrongTypedef<T>{}, Value> Serialize(
const T& object, formats::
serialize::To<Value>) {
339 impl::strong_typedef::CheckIfAllowsLogging<T>();
340 return typename Value::Builder(object.GetUnderlying()).ExtractValue();
343template <
typename T,
typename StringBuilder>
344std::enable_if_t<IsStrongTypedef<T>{}> WriteToStream(
const T& object, StringBuilder& sw) {
345 impl::strong_typedef::CheckIfAllowsLogging<T>();
346 WriteToStream(object.GetUnderlying(), sw);
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();
355template <
typename Tag,
typename T, StrongTypedefOps Ops, std::enable_if_t<meta::kIsInteger<T>,
bool> =
true>
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());
361template <
typename Tag,
typename T, StrongTypedefOps Ops, std::enable_if_t<std::is_floating_point_v<T>,
bool> =
true>
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());
372template <
typename Target,
typename Tag,
typename T, StrongTypedefOps Ops,
typename Enable>
373constexpr Target
StrongCast(
const StrongTypedef<Tag, T, Ops, Enable>& src) {
374 static_assert(IsStrongTypedef<Target>{},
"Expected strong typedef as target type");
376 std::is_convertible_v<T,
typename Target::UnderlyingType>,
377 "Source strong typedef underlying type must be convertible to "
378 "target's underlying type"
380 return Target{src.GetUnderlying()};
383template <
typename Target,
typename Tag,
typename T, StrongTypedefOps Ops,
typename Enable>
384constexpr Target StrongCast(StrongTypedef<Tag, T, Ops, Enable>&& src) {
385 static_assert(IsStrongTypedef<Target>{},
"Expected strong typedef as target type");
387 std::is_convertible_v<T,
typename Target::UnderlyingType>,
388 "Source strong typedef underlying type must be convertible to "
389 "target's underlying type"
391 return Target{std::move(src).GetUnderlying()};
394template <
class Tag,
class T, StrongTypedefOps Ops>
395std::size_t hash_value(
const StrongTypedef<Tag, T, Ops>& v) {
396 return boost::hash<T>{}(v.GetUnderlying());
400template <
class Tag,
class T, StrongTypedefOps Ops>
401void PrintTo(
const StrongTypedef<Tag, T, Ops>& v, std::ostream* os) {
402 *os << testing::PrintToString(v.GetUnderlying());
409template <
class Tag,
class T>
410using NonLoggable = StrongTypedef<Tag, T, StrongTypedefOps::kCompareStrong | StrongTypedefOps::kNonLoggable>;
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());
426template <
class T,
class Char>
427struct fmt::formatter<T, Char, std::enable_if_t<USERVER_NAMESPACE::
utils::IsStrongTypedef<T>{}>>
428 : fmt::formatter<
typename T::UnderlyingType, Char> {
429 template <
typename FormatContext>
431 USERVER_NAMESPACE::
utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
432 return fmt::formatter<
typename T::UnderlyingType, Char>::format(v.GetUnderlying(), ctx);