13#include <fmt/format.h>
14#include <userver/utils/fmt_compat.hpp>
16#include <boost/functional/hash_fwd.hpp>
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>
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();
97concept Range = meta::kIsRange<T> && !meta::kIsInstantiationOf<std::basic_string, std::remove_const_t<T>>;
100constexpr void CheckIfAllowsLogging() {
101 static_assert(IsStrongTypedef<T>);
103 if constexpr (T::kOps & StrongTypedefOps::kNonLoggable) {
104 static_assert(!
sizeof(T),
"Trying to print a non-loggable StrongTypedef");
108template <
class To,
class... Args>
109constexpr bool IsStrongToStrongConversion()
noexcept {
110 static_assert(IsStrongTypedef<To>);
112 if constexpr (
sizeof...(Args) == 1) {
113 using FromDecayed = std::decay_t<
decltype((std::declval<Args>(), ...))>;
114 if constexpr (IsStrongTypedef<FromDecayed>) {
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>);
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);
167 static_assert(!std::is_reference<Tag>::value);
168 static_assert(!std::is_pointer<Tag>::value);
171 using UnderlyingType = T;
173 static constexpr StrongTypedefOps kOps = Ops;
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;
181 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst)
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)...)
190 using impl::strong_typedef::IsStrongToStrongConversion;
192 !IsStrongToStrongConversion<StrongTypedef, Args...>(),
193 "Attempt to convert one StrongTypedef to another. Use "
194 "utils::StrongCast to do that"
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_; }
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_; }
207 requires impl::strong_typedef::Range<T>
209 return std::begin(data_);
213 requires impl::strong_typedef::Range<T>
215 return std::end(data_);
219 requires impl::strong_typedef::Range<T>
221 return std::begin(data_);
225 requires impl::strong_typedef::Range<T>
227 return std::end(data_);
231 requires impl::strong_typedef::Range<T>
233 return std::cbegin(data_);
237 requires impl::strong_typedef::Range<T>
239 return std::cend(data_);
243 requires meta::kIsSizable<T>
245 return std::size(data_);
248 auto empty()
const {
return data_.empty(); }
250 auto clear() {
return data_.clear(); }
253 decltype(
auto) operator[](Arg&& i) {
254 return data_[std::forward<Arg>(i)];
257 decltype(
auto) operator[](Arg&& i)
const {
258 return data_[std::forward<Arg>(i)];
268#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
273 impl::strong_typedef::IsStrongTypedef<T> || impl::strong_typedef::IsStrongTypedef<U>,
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();
284 impl::strong_typedef::CheckTransparentCompare<T>();
285 return lhs.GetUnderlying() OPERATOR rhs;
288 impl::strong_typedef::CheckTransparentCompare<U>();
289 return lhs OPERATOR rhs.GetUnderlying();
301#undef UTILS_STRONG_TYPEDEF_REL_OP
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();
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();
318template <
class Tag,
class T, StrongTypedefOps Ops>
319constexpr decltype(
auto) UnderlyingValue(
const StrongTypedef<Tag, T, Ops>& v)
noexcept {
320 return v.GetUnderlying();
323template <
class Tag,
class T, StrongTypedefOps Ops>
324constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v)
noexcept {
325 return std::move(v).GetUnderlying();
328constexpr bool IsStrongTypedefLoggable(StrongTypedefOps ops) {
return !(ops & StrongTypedefOps::kNonLoggable); }
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>()};
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();
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);
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, 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());
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());
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");
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>
384constexpr Target StrongCast(StrongTypedef<Tag, T, Ops>&& src) {
385 static_assert(impl::strong_typedef::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 <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>
430 USERVER_NAMESPACE::
utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
431 return fmt::formatter<
typename T::UnderlyingType, Char>::format(v.GetUnderlying(), ctx);