12#include <fmt/format.h>
13#include <userver/utils/fmt_compat.hpp>
15#include <boost/functional/hash_fwd.hpp>
17#include <userver/formats/common/meta.hpp>
18#include <userver/utils/meta.hpp>
19#include <userver/utils/underlying_value.hpp>
20#include <userver/utils/void_t.hpp>
25std::string PrintToString(
const T& value);
29USERVER_NAMESPACE_BEGIN
48 return utils::UnderlyingValue(op) &
utils::UnderlyingValue(mask);
52 return StrongTypedefOps{utils::UnderlyingValue(op1) | utils::UnderlyingValue(op2)};
94namespace impl::strong_typedef {
96template <
class T,
class = void_t<>>
97struct InitializerListImpl {
99 using type = DoNotMatch;
103struct InitializerListImpl<T, void_t<
typename T::value_type>> {
104 using type = std::initializer_list<
typename T::value_type>;
110using InitializerList =
typename InitializerListImpl<T>::type;
112template <
class T,
class U>
113constexpr void CheckStrongCompare() {
115 std::is_same_v<
typename T::UnderlyingType,
typename U::UnderlyingType> &&
116 std::is_same_v<
typename T::TagType,
typename U::TagType> && T::kOps == U::kOps &&
118 "Comparing those StrongTypedefs is forbidden"
122template <
class Typedef>
123constexpr void CheckTransparentCompare() {
126 "Comparing this StrongTypedef to a raw value is forbidden"
130struct StrongTypedefTag {};
133using IsStrongTypedef =
134 std::conjunction<std::is_base_of<StrongTypedefTag, T>, std::is_convertible<T&, StrongTypedefTag&>>;
137const auto& UnwrapIfStrongTypedef(
const T& value) {
138 if constexpr (IsStrongTypedef<T>{}) {
139 return value.GetUnderlying();
147template <
typename T,
typename Void>
148using EnableIfRange = std::enable_if_t<
149 std::is_void_v<Void> && meta::kIsRange<T> && !meta::kIsInstantiationOf<std::basic_string, std::remove_const_t<T>>>;
151template <
typename T,
typename Void>
152using EnableIfSizeable = std::enable_if_t<std::is_void_v<Void> && meta::kIsSizable<T>>;
155constexpr void CheckIfAllowsLogging() {
156 static_assert(IsStrongTypedef<T>::value);
159 static_assert(!
sizeof(T),
"Trying to print a non-loggable StrongTypedef");
163template <
class To,
class... Args>
164constexpr bool IsStrongToStrongConversion()
noexcept {
165 static_assert(IsStrongTypedef<To>::value);
167 if constexpr (
sizeof...(Args) == 1) {
168 using FromDecayed = std::decay_t<
decltype((std::declval<Args>(), ...))>;
169 if constexpr (IsStrongTypedef<FromDecayed>::value) {
171 return !std::is_same_v<FromDecayed, To> &&
172 (std::is_same_v<
typename FromDecayed::UnderlyingType,
typename To::UnderlyingType> ||
173 std::is_arithmetic_v<
typename To::UnderlyingType>);
182using impl::strong_typedef::IsStrongTypedef;
185class StrongTypedef :
public impl::strong_typedef::StrongTypedefTag {
186 static_assert(!std::is_reference<T>::value);
187 static_assert(!std::is_pointer<T>::value);
189 static_assert(!std::is_reference<Tag>::value);
190 static_assert(!std::is_pointer<Tag>::value);
193 using UnderlyingType = T;
197 StrongTypedef() =
default;
198 StrongTypedef(
const StrongTypedef&) =
default;
199 StrongTypedef(StrongTypedef&&)
noexcept =
default;
200 StrongTypedef& operator=(
const StrongTypedef&) =
default;
201 StrongTypedef& operator=(StrongTypedef&&)
noexcept =
default;
203 constexpr StrongTypedef(impl::strong_typedef::InitializerList<T> lst) : data_(lst) {}
205 template <
typename... Args,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
206 explicit constexpr StrongTypedef(Args&&... args)
noexcept(
noexcept(T(std::forward<Args>(args)...)))
207 : data_(std::forward<Args>(args)...) {
208 using impl::strong_typedef::IsStrongToStrongConversion;
210 !IsStrongToStrongConversion<StrongTypedef, Args...>(),
211 "Attempt to convert one StrongTypedef to another. Use "
212 "utils::StrongCast to do that"
216 explicit constexpr operator
const T&()
const&
noexcept {
return data_; }
217 explicit constexpr operator T() &&
noexcept {
return std::move(data_); }
218 explicit constexpr operator T&() &
noexcept {
return data_; }
220 constexpr const T& GetUnderlying()
const&
noexcept {
return data_; }
221 constexpr T GetUnderlying() &&
noexcept {
return std::move(data_); }
222 constexpr T& GetUnderlying() &
noexcept {
return data_; }
224 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
226 return std::begin(data_);
229 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<T, Void>>
231 return std::end(data_);
234 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
236 return std::begin(data_);
239 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
241 return std::end(data_);
244 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
245 auto cbegin()
const {
246 return std::cbegin(data_);
249 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfRange<
const T, Void>>
251 return std::cend(data_);
254 template <
typename Void =
void,
typename = impl::strong_typedef::EnableIfSizeable<
const T, Void>>
256 return std::size(data_);
259 auto empty()
const {
return data_.empty(); }
261 auto clear() {
return data_.clear(); }
264 decltype(
auto) operator[](Arg&& i) {
265 return data_[std::forward<Arg>(i)];
268 decltype(
auto) operator[](Arg&& i)
const {
269 return data_[std::forward<Arg>(i)];
279#define UTILS_STRONG_TYPEDEF_REL_OP(OPERATOR)
284 impl::strong_typedef::IsStrongTypedef<T>{} || impl::strong_typedef::IsStrongTypedef<U>{},
287 constexpr auto operator
OPERATOR(const T& lhs, const U& rhs)
288 ->decltype(impl::strong_typedef::UnwrapIfStrongTypedef(lhs)
289 OPERATOR impl::strong_typedef::UnwrapIfStrongTypedef(rhs)) {
290 if constexpr (impl::strong_typedef::IsStrongTypedef<T>{}) {
291 if constexpr (impl::strong_typedef::IsStrongTypedef<U>{}) {
292 impl::strong_typedef::CheckStrongCompare<T, U>();
293 return lhs.GetUnderlying() OPERATOR rhs.GetUnderlying();
295 impl::strong_typedef::CheckTransparentCompare<T>();
296 return lhs.GetUnderlying() OPERATOR rhs;
299 impl::strong_typedef::CheckTransparentCompare<U>();
300 return lhs OPERATOR rhs.GetUnderlying();
311#if __cpp_lib_three_way_comparison >= 201711L
|| defined(ARCADIA_ROOT)
315#undef UTILS_STRONG_TYPEDEF_REL_OP
320std::ostream& operator<<(std::ostream& os,
const StrongTypedef<Tag, T, Ops>& v) {
321 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
322 return os << v.GetUnderlying();
326logging::LogHelper& operator<<(
logging::LogHelper& os,
const StrongTypedef<Tag, T, Ops>& v) {
327 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, T, Ops>>();
328 return os << v.GetUnderlying();
333constexpr decltype(
auto) UnderlyingValue(
const StrongTypedef<Tag, T, Ops>& v)
noexcept {
334 return v.GetUnderlying();
338constexpr T UnderlyingValue(StrongTypedef<Tag, T, Ops>&& v)
noexcept {
339 return std::move(v).GetUnderlying();
346template <
typename T,
typename Value>
347std::enable_if_t<formats::common::kIsFormatValue<Value> && IsStrongTypedef<T>{}, T>
348Parse(
const Value& source, formats::parse::To<T>) {
349 return T{source.
template As<
typename T::UnderlyingType>()};
352template <
typename T,
typename Value>
353std::enable_if_t<IsStrongTypedef<T>{}, Value> Serialize(
const T& object, formats::serialize::To<Value>) {
354 impl::strong_typedef::CheckIfAllowsLogging<T>();
355 return typename Value::Builder(object.GetUnderlying()).ExtractValue();
358template <
typename T,
typename StringBuilder>
359std::enable_if_t<IsStrongTypedef<T>{}> WriteToStream(
const T& object, StringBuilder& sw) {
360 impl::strong_typedef::CheckIfAllowsLogging<T>();
361 WriteToStream(object.GetUnderlying(), sw);
365std::string ToString(
const StrongTypedef<Tag, std::string, Ops>& object) {
366 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, std::string, Ops>>();
367 return object.GetUnderlying();
370template <
typename Tag,
typename T,
StrongTypedefOps Ops, std::enable_if_t<meta::kIsInteger<T>,
bool> =
true>
371std::string ToString(
const StrongTypedef<Tag, T, Ops>& object) {
372 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, std::string, Ops>>();
373 return std::to_string(object.GetUnderlying());
376template <
typename Tag,
typename T,
StrongTypedefOps Ops, std::enable_if_t<std::is_floating_point_v<T>,
bool> =
true>
377std::string ToString(
const StrongTypedef<Tag, T, Ops>& object) {
378 impl::strong_typedef::CheckIfAllowsLogging<StrongTypedef<Tag, std::string, Ops>>();
379 return fmt::to_string(object.GetUnderlying());
387template <
typename Target,
typename Tag,
typename T,
StrongTypedefOps Ops,
typename Enable>
388constexpr Target
StrongCast(
const StrongTypedef<Tag, T, Ops, Enable>& src) {
389 static_assert(IsStrongTypedef<Target>{},
"Expected strong typedef as target type");
391 std::is_convertible_v<T,
typename Target::UnderlyingType>,
392 "Source strong typedef underlying type must be convertible to "
393 "target's underlying type"
395 return Target{src.GetUnderlying()};
398template <
typename Target,
typename Tag,
typename T,
StrongTypedefOps Ops,
typename Enable>
399constexpr Target StrongCast(StrongTypedef<Tag, T, Ops, Enable>&& src) {
400 static_assert(IsStrongTypedef<Target>{},
"Expected strong typedef as target type");
402 std::is_convertible_v<T,
typename Target::UnderlyingType>,
403 "Source strong typedef underlying type must be convertible to "
404 "target's underlying type"
406 return Target{std::move(src).GetUnderlying()};
410std::size_t hash_value(
const StrongTypedef<Tag, T, Ops>& v) {
411 return boost::hash<T>{}(v.GetUnderlying());
416void PrintTo(
const StrongTypedef<Tag, T, Ops>& v, std::ostream* os) {
417 *os << testing::PrintToString(v.GetUnderlying());
424template <
class Tag,
class T>
432template <
class Tag,
class T, USERVER_NAMESPACE::utils::StrongTypedefOps Ops>
433struct std::hash<USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops>> : std::hash<T> {
434 std::size_t operator()(
const USERVER_NAMESPACE::utils::StrongTypedef<Tag, T, Ops>& v)
const
435 noexcept(
noexcept(std::declval<
const std::hash<T>>()(std::declval<
const T&>()))) {
436 return std::hash<T>::operator()(v.GetUnderlying());
441template <
class T,
class Char>
442struct fmt::formatter<T, Char, std::enable_if_t<USERVER_NAMESPACE::utils::IsStrongTypedef<T>{}>>
443 : fmt::formatter<
typename T::UnderlyingType, Char> {
444 template <
typename FormatContext>
446 USERVER_NAMESPACE::utils::impl::strong_typedef::CheckIfAllowsLogging<T>();
447 return fmt::formatter<
typename T::UnderlyingType, Char>::format(v.GetUnderlying(), ctx);