12#include <fmt/format.h>
13#include <userver/utils/fmt_compat.hpp>
15#include <boost/functional/hash_fwd.hpp>
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>
26std::string PrintToString(
const T& value);
30USERVER_NAMESPACE_BEGIN
95namespace impl::strong_typedef {
97template <
class T,
class = void_t<>>
98struct InitializerListImpl {
100 using type = DoNotMatch;
104struct InitializerListImpl<T, void_t<
typename T::value_type>> {
105 using type = std::initializer_list<
typename T::value_type>;
111using InitializerList =
typename InitializerListImpl<T>::type;
113template <
class T,
class U>
114constexpr void CheckStrongCompare() {
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"
123template <
class Typedef>
124constexpr void CheckTransparentCompare() {
127 "Comparing this StrongTypedef to a raw value is forbidden"
131struct StrongTypedefTag {};
134using IsStrongTypedef =
135 std::conjunction<std::is_base_of<StrongTypedefTag, T>, std::is_convertible<T&, StrongTypedefTag&>>;
138const auto& UnwrapIfStrongTypedef(
const T& value) {
139 if constexpr (IsStrongTypedef<T>{}) {
140 return value.GetUnderlying();
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>>>;
152template <
typename T,
typename Void>
153using EnableIfSizeable = std::enable_if_t<std::is_void_v<Void> && meta::kIsSizable<T>>;
156constexpr void CheckIfAllowsLogging() {
157 static_assert(IsStrongTypedef<T>::value);
160 static_assert(!
sizeof(T),
"Trying to print a non-loggable StrongTypedef");
164template <
class To,
class... Args>
165constexpr bool IsStrongToStrongConversion()
noexcept {
166 static_assert(IsStrongTypedef<To>::value);
168 if constexpr (
sizeof...(Args) == 1) {
169 using FromDecayed = std::decay_t<
decltype((std::declval<Args>(), ...))>;
170 if constexpr (IsStrongTypedef<FromDecayed>::value) {
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>);
183using impl::strong_typedef::IsStrongTypedef;
186class StrongTypedef :
public impl::strong_typedef::StrongTypedefTag {
187 static_assert(!std::is_reference<T>::value);
188 static_assert(!std::is_pointer<T>::value);
190 static_assert(!std::is_reference<Tag>::value);
191 static_assert(!std::is_pointer<Tag>::value);
194 using UnderlyingType = T;
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;
204 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst) : data_(lst) {}
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;
211 !IsStrongToStrongConversion<StrongTypedef, Args...>(),
212 "Attempt to convert one StrongTypedef to another. Use "
213 "utils::StrongCast to do that"
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_; }
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_; }
225 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
227 return std::begin(data_);
230 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
232 return std::end(data_);
235 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
237 return std::begin(data_);
240 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
242 return std::end(data_);
245 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
246 auto cbegin()
const {
247 return std::cbegin(data_);
250 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
252 return std::cend(data_);
255 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfSizeable<
const T, Void>>
257 return std::size(data_);
260 auto empty()
const {
return data_.empty(); }
262 auto clear() {
return data_.clear(); }
265 decltype(
auto) operator[](Arg&& i) {
266 return data_[std::forward<Arg>(i)];
269 decltype(
auto) operator[](Arg&& i)
const {
270 return data_[std::forward<Arg>(i)];
280#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
285 impl::strong_typedef::IsStrongTypedef<T>{} || impl::strong_typedef::IsStrongTypedef<U>{},
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();
296 impl::strong_typedef::CheckTransparentCompare<T>();
297 return lhs.GetUnderlying() OPERATOR rhs;
300 impl::strong_typedef::CheckTransparentCompare<U>();
301 return lhs OPERATOR rhs.GetUnderlying();
312#ifdef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
316#undef UTILS_STRONG_TYPEDEF_REL_OP
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();
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();
334constexpr decltype(
auto) UnderlyingValue(
const StrongTypedef<Tag, T, Ops>& v)
noexcept {
335 return v.GetUnderlying();
339constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v)
noexcept {
340 return std::move(v).GetUnderlying();
347template <
typename T,
typename Value>
348std::enable_if_t<
formats::
common::kIsFormatValue<Value> && IsStrongTypedef<T>{}, T>
350 return T{source.
template As<
typename T::UnderlyingType>()};
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();
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);
366std::string ToString(
const StrongTypedef<Tag, std::string, Ops>& object) {
367 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, std::string, Ops>>();
368 return object.GetUnderlying();
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());
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());
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");
392 std::is_convertible_v<T,
typename Target::UnderlyingType>,
393 "Source strong typedef underlying type must be convertible to "
394 "target's underlying type"
396 return Target{src.GetUnderlying()};
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");
403 std::is_convertible_v<T,
typename Target::UnderlyingType>,
404 "Source strong typedef underlying type must be convertible to "
405 "target's underlying type"
407 return Target{std::move(src).GetUnderlying()};
411std::size_t hash_value(
const StrongTypedef<Tag, T, Ops>& v) {
412 return boost::hash<T>{}(v.GetUnderlying());
417void PrintTo(
const StrongTypedef<Tag, T, Ops>& v, std::ostream* os) {
418 *os << testing::PrintToString(v.GetUnderlying());
425template <
class Tag,
class T>
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());
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>
447 USERVER_NAMESPACE::
utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
448 return fmt::formatter<
typename T::UnderlyingType, Char>::format(v.GetUnderlying(), ctx);