aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorMaxim Yurchuk <maxim-yurchuk@ydb.tech>2024-11-13 16:32:39 +0300
committerMaxim Yurchuk <maxim-yurchuk@ydb.tech>2024-11-13 16:32:39 +0300
commit23387aafadce23ea77beffb2981e8c0e2f2a7f0a (patch)
tree54b0f95fca1ad03d93f5a18abdfe98e8ab34b420 /library/cpp
parentee51155da394b56a8e3329d25a514422e5da7bc3 (diff)
parent4ab23311f7a6d45ac318179569df9ba46fb9ab68 (diff)
downloadydb-23387aafadce23ea77beffb2981e8c0e2f2a7f0a.tar.gz
Merge branch 'rightlib' into mergelibs-yurchuk-manual
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/blockcodecs/core/codecs.h11
-rw-r--r--library/cpp/containers/concurrent_hash/concurrent_hash.h21
-rw-r--r--library/cpp/http/misc/httpcodes.h1
-rw-r--r--library/cpp/svnversion/test/main.cpp1
-rw-r--r--library/cpp/tld/tlds-alpha-by-domain.txt2
-rw-r--r--library/cpp/yt/assert/assert.h14
-rw-r--r--library/cpp/yt/error/convert_to_cpo.h43
-rw-r--r--library/cpp/yt/error/error_attributes-inl.h31
-rw-r--r--library/cpp/yt/error/error_attributes.h13
-rw-r--r--library/cpp/yt/memory/ref-inl.h11
-rw-r--r--library/cpp/yt/memory/ref.cpp19
-rw-r--r--library/cpp/yt/memory/ref.h31
-rw-r--r--library/cpp/yt/misc/tag_invoke.h95
-rw-r--r--library/cpp/yt/misc/tag_invoke_cpo.h25
-rw-r--r--library/cpp/yt/misc/unittests/tag_invoke_cpo_ut.cpp107
-rw-r--r--library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp72
-rw-r--r--library/cpp/yt/misc/unittests/ya.make2
17 files changed, 474 insertions, 25 deletions
diff --git a/library/cpp/blockcodecs/core/codecs.h b/library/cpp/blockcodecs/core/codecs.h
index 6512abddefc..61efc04744a 100644
--- a/library/cpp/blockcodecs/core/codecs.h
+++ b/library/cpp/blockcodecs/core/codecs.h
@@ -26,13 +26,14 @@ namespace NBlockCodecs {
{
}
- template <>
- inline TData(const TString& t)
- : TStringBuf((const char*)t.data(), t.size())
- {
- }
};
+ template <>
+ inline TData::TData(const TString& t)
+ : TStringBuf((const char*)t.data(), t.size())
+ {
+ }
+
struct TCodecError: public yexception {
};
diff --git a/library/cpp/containers/concurrent_hash/concurrent_hash.h b/library/cpp/containers/concurrent_hash/concurrent_hash.h
index 88da30fd78d..74573ecc095 100644
--- a/library/cpp/containers/concurrent_hash/concurrent_hash.h
+++ b/library/cpp/containers/concurrent_hash/concurrent_hash.h
@@ -12,11 +12,12 @@ namespace NPrivate {
};
}
-template <typename K, typename V, size_t BucketCount = 64, typename L = TAdaptiveLock>
+template <typename K, typename V, size_t BucketCount = 64, typename L = TAdaptiveLock, class TLockOps = TCommonLockOps<L>>
class TConcurrentHashMap {
public:
using TActualMap = THashMap<K, V>;
using TLock = L;
+ using TBucketGuard = TGuard<TLock, TLockOps>;
struct TBucket {
friend class TConcurrentHashMap;
@@ -88,13 +89,13 @@ public:
void Insert(const K& key, const V& value) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
bucket.Map[key] = value;
}
void InsertUnique(const K& key, const V& value) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
if (!bucket.Map.insert(std::make_pair(key, value)).second) {
Y_ABORT("non-unique key");
}
@@ -102,14 +103,14 @@ public:
V& InsertIfAbsent(const K& key, const V& value) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
return bucket.Map.insert(std::make_pair(key, value)).first->second;
}
template <typename TKey, typename... Args>
V& EmplaceIfAbsent(TKey&& key, Args&&... args) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
if (V* value = bucket.TryGetUnsafe(key)) {
return *value;
}
@@ -123,7 +124,7 @@ public:
template <typename Callable>
V& InsertIfAbsentWithInit(const K& key, Callable initFunc) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
if (V* value = bucket.TryGetUnsafe(key)) {
return *value;
}
@@ -133,13 +134,13 @@ public:
V Get(const K& key) const {
const TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
return bucket.GetUnsafe(key);
}
bool Get(const K& key, V& result) const {
const TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
if (const V* value = bucket.TryGetUnsafe(key)) {
result = *value;
return true;
@@ -149,13 +150,13 @@ public:
V Remove(const K& key) {
TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
return bucket.RemoveUnsafe(key);
}
bool Has(const K& key) const {
const TBucket& bucket = GetBucketForKey(key);
- TGuard<TLock> guard(bucket.Mutex);
+ TBucketGuard guard(bucket.Mutex);
return bucket.HasUnsafe(key);
}
};
diff --git a/library/cpp/http/misc/httpcodes.h b/library/cpp/http/misc/httpcodes.h
index 4565b1c581b..b9407ec8eec 100644
--- a/library/cpp/http/misc/httpcodes.h
+++ b/library/cpp/http/misc/httpcodes.h
@@ -57,6 +57,7 @@ enum HttpCodes {
HTTP_PRECONDITION_REQUIRED = 428,
HTTP_TOO_MANY_REQUESTS = 429,
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ HTTP_REQUESTED_HOST_UNAVAILABLE = 434,
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
HTTP_INTERNAL_SERVER_ERROR = 500,
diff --git a/library/cpp/svnversion/test/main.cpp b/library/cpp/svnversion/test/main.cpp
index 830686d5b3d..632c8d3915b 100644
--- a/library/cpp/svnversion/test/main.cpp
+++ b/library/cpp/svnversion/test/main.cpp
@@ -8,6 +8,7 @@
int main() {
Cout << "GetProgramSvnVersion(): " << GetProgramSvnVersion() << Endl;
Cout << "GetCustomVersion(): " << GetCustomVersion() << Endl;
+ Cout << "GetReleaseVersion(): " << GetReleaseVersion() << Endl;
Cout << "PrintProgramSvnVersion(): " << Endl; PrintProgramSvnVersion();
Cout << "GetArcadiaSourcePath(): " << GetArcadiaSourcePath() << Endl;
Cout << "GetArcadiaSourceUrl(): " << GetArcadiaSourceUrl() << Endl;
diff --git a/library/cpp/tld/tlds-alpha-by-domain.txt b/library/cpp/tld/tlds-alpha-by-domain.txt
index 2a7c080416b..3b4fd6d6821 100644
--- a/library/cpp/tld/tlds-alpha-by-domain.txt
+++ b/library/cpp/tld/tlds-alpha-by-domain.txt
@@ -1,4 +1,4 @@
-# Version 2024110700, Last Updated Thu Nov 7 07:07:01 2024 UTC
+# Version 2024111000, Last Updated Sun Nov 10 07:07:01 2024 UTC
AAA
AARP
ABB
diff --git a/library/cpp/yt/assert/assert.h b/library/cpp/yt/assert/assert.h
index 7a9e761a3a9..f2131d5bbcd 100644
--- a/library/cpp/yt/assert/assert.h
+++ b/library/cpp/yt/assert/assert.h
@@ -55,6 +55,20 @@ void AssertTrapImpl(
} \
} while (false)
+//! Behaves as |YT_ASSERT| in debug mode and as |Y_ASSUME| in release.
+#ifdef NDEBUG
+ #define YT_ASSUME(expr) Y_ASSUME(expr)
+#else
+ #define YT_ASSUME(expr) YT_ASSERT(expr)
+#endif
+
+//! Behaves as |YT_ASSERT(false)| in debug mode and as |Y_UNREACHABLE| in release.
+#ifdef NDEBUG
+ #define YT_UNREACHABLE() Y_UNREACHABLE()
+#else
+ #define YT_UNREACHABLE() YT_ASSERT(false)
+#endif
+
//! Fatal error code marker. Abnormally terminates the current process.
#ifdef YT_COMPILING_UDF
#define YT_ABORT() __YT_BUILTIN_ABORT()
diff --git a/library/cpp/yt/error/convert_to_cpo.h b/library/cpp/yt/error/convert_to_cpo.h
new file mode 100644
index 00000000000..55f608c3b60
--- /dev/null
+++ b/library/cpp/yt/error/convert_to_cpo.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <library/cpp/yt/misc/tag_invoke_cpo.h>
+
+#include <util/generic/strbuf.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): This file intentionally is unaware of possible "yson-implementations"
+// e.g. INode and TYsonString(Buf). This is done for two reasons:
+// 1) We can't have dep on INodePtr here anyway.
+// 2) We would like to eventually remove dep on yson of this module.
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): We intentionally generate a separate namespace
+// where ConvertToImpl functions would reside
+// without polluting general-use namespaces.
+namespace NConvertToImpl {
+
+template <class T>
+struct TFn : public NYT::TTagInvokeCpoBase<TFn<T>>
+{ };
+
+} // NConvertToImpl
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+inline constexpr NConvertToImpl::TFn<T> ConvertTo = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TTo, class TFrom>
+concept CConvertToWorks = requires (const TFrom& from) {
+ { NYT::ConvertTo<TTo>(from) } -> std::same_as<TTo>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error_attributes-inl.h b/library/cpp/yt/error/error_attributes-inl.h
index a05e62d0e91..8ffbf3e0a39 100644
--- a/library/cpp/yt/error/error_attributes-inl.h
+++ b/library/cpp/yt/error/error_attributes-inl.h
@@ -9,6 +9,34 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
+T TErrorAttributes::Get(TStringBuf key) const
+{
+ auto yson = GetYson(key);
+ try {
+ return NYT::ConvertTo<T>(yson);
+ } catch (const std::exception& ex) {
+ ThrowCannotParseAttributeException(key, ex);
+ }
+}
+
+template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
+typename TOptionalTraits<T>::TOptional TErrorAttributes::Find(TStringBuf key) const
+{
+ auto yson = FindYson(key);
+ if (!yson) {
+ return typename TOptionalTraits<T>::TOptional();
+ }
+ try {
+ return NYT::ConvertTo<T>(yson);
+ } catch (const std::exception& ex) {
+ ThrowCannotParseAttributeException(key, ex);
+ }
+}
+
+template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
T TErrorAttributes::GetAndRemove(const TString& key)
{
auto result = Get<T>(key);
@@ -17,12 +45,14 @@ T TErrorAttributes::GetAndRemove(const TString& key)
}
template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
T TErrorAttributes::Get(TStringBuf key, const T& defaultValue) const
{
return Find<T>(key).value_or(defaultValue);
}
template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
T TErrorAttributes::GetAndRemove(const TString& key, const T& defaultValue)
{
auto result = Find<T>(key);
@@ -35,6 +65,7 @@ T TErrorAttributes::GetAndRemove(const TString& key, const T& defaultValue)
}
template <class T>
+ requires CConvertToWorks<T, TErrorAttributes::TValue>
typename TOptionalTraits<T>::TOptional TErrorAttributes::FindAndRemove(const TString& key)
{
auto result = Find<T>(key);
diff --git a/library/cpp/yt/error/error_attributes.h b/library/cpp/yt/error/error_attributes.h
index 88771f83601..7c2f4a54078 100644
--- a/library/cpp/yt/error/error_attributes.h
+++ b/library/cpp/yt/error/error_attributes.h
@@ -1,5 +1,6 @@
#pragma once
+#include "convert_to_cpo.h"
#include "mergeable_dictionary.h"
#include <library/cpp/yt/misc/optional.h>
@@ -13,6 +14,9 @@ namespace NYT {
// Eventually it would be a simple hash map.
// NB(arkady-e1ppa): For now most methods are defined in yt/yt/core/misc/stripped_error.cpp
// eventually they will be moved here.
+// NB(arkady-e1ppa): For now we use TYsonString as value.
+// TODO(arkady-e1ppa): Try switching to TString/std::string eventually.
+// representing text-encoded yson string eventually (maybe).
class TErrorAttributes
{
public:
@@ -54,28 +58,34 @@ public:
//! Finds the attribute and deserializes its value.
//! Throws if no such value is found.
template <class T>
+ requires CConvertToWorks<T, TValue>
T Get(TStringBuf key) const;
//! Same as #Get but removes the value.
template <class T>
+ requires CConvertToWorks<T, TValue>
T GetAndRemove(const TString& key);
//! Finds the attribute and deserializes its value.
//! Uses default value if no such attribute is found.
template <class T>
+ requires CConvertToWorks<T, TValue>
T Get(TStringBuf key, const T& defaultValue) const;
//! Same as #Get but removes the value if it exists.
template <class T>
+ requires CConvertToWorks<T, TValue>
T GetAndRemove(const TString& key, const T& defaultValue);
//! Finds the attribute and deserializes its value.
//! Returns null if no such attribute is found.
template <class T>
+ requires CConvertToWorks<T, TValue>
typename TOptionalTraits<T>::TOptional Find(TStringBuf key) const;
//! Same as #Find but removes the value if it exists.
template <class T>
+ requires CConvertToWorks<T, TValue>
typename TOptionalTraits<T>::TOptional FindAndRemove(const TString& key);
template <CMergeableDictionary TDictionary>
@@ -92,6 +102,9 @@ private:
TErrorAttributes(TErrorAttributes&& other) = default;
TErrorAttributes& operator= (TErrorAttributes&& other) = default;
+
+ // defined in yt/yt/core/misc/stripped_error.cpp right now.
+ [[noreturn]] static void ThrowCannotParseAttributeException(TStringBuf key, const std::exception& ex);
};
bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs);
diff --git a/library/cpp/yt/memory/ref-inl.h b/library/cpp/yt/memory/ref-inl.h
index 7fcd91908cf..a2074f97311 100644
--- a/library/cpp/yt/memory/ref-inl.h
+++ b/library/cpp/yt/memory/ref-inl.h
@@ -142,6 +142,17 @@ Y_FORCE_INLINE TSharedRef TSharedRef::FromString(TString str)
return FromString<TDefaultSharedBlobTag>(std::move(str));
}
+template <class TTag>
+Y_FORCE_INLINE TSharedRef TSharedRef::FromString(std::string str)
+{
+ return FromString(std::move(str), GetRefCountedTypeCookie<TTag>());
+}
+
+Y_FORCE_INLINE TSharedRef TSharedRef::FromString(std::string str)
+{
+ return FromString<TDefaultSharedBlobTag>(std::move(str));
+}
+
Y_FORCE_INLINE TStringBuf TSharedRef::ToStringBuf() const
{
return TStringBuf(Begin(), Size());
diff --git a/library/cpp/yt/memory/ref.cpp b/library/cpp/yt/memory/ref.cpp
index 4d02ef6875b..de04a83870e 100644
--- a/library/cpp/yt/memory/ref.cpp
+++ b/library/cpp/yt/memory/ref.cpp
@@ -46,6 +46,7 @@ private:
////////////////////////////////////////////////////////////////////////////////
+template <class TString>
class TStringHolder
: public TSharedRangeHolder
{
@@ -232,11 +233,27 @@ TMutableRef TMutableRef::FromBlob(TBlob& blob)
TSharedRef TSharedRef::FromString(TString str, TRefCountedTypeCookie tagCookie)
{
- auto holder = New<TStringHolder>(std::move(str), tagCookie);
+ return FromStringImpl(std::move(str), tagCookie);
+}
+
+TSharedRef TSharedRef::FromString(std::string str, TRefCountedTypeCookie tagCookie)
+{
+ return FromStringImpl(std::move(str), tagCookie);
+}
+
+template <class TString>
+TSharedRef TSharedRef::FromStringImpl(TString str, TRefCountedTypeCookie tagCookie)
+{
+ auto holder = New<TStringHolder<TString>>(std::move(str), tagCookie);
auto ref = TRef::FromString(holder->String());
return TSharedRef(ref, std::move(holder));
}
+TSharedRef TSharedRef::FromString(const char* str)
+{
+ return FromString(std::string(str));
+}
+
TSharedRef TSharedRef::FromBlob(TBlob&& blob)
{
auto ref = TRef::FromBlob(blob);
diff --git a/library/cpp/yt/memory/ref.h b/library/cpp/yt/memory/ref.h
index 78ea46675fc..fa40cd9afbd 100644
--- a/library/cpp/yt/memory/ref.h
+++ b/library/cpp/yt/memory/ref.h
@@ -134,22 +134,34 @@ public:
operator TRef() const;
- //! Creates a TSharedRef from a string.
- //! Since strings are ref-counted, no data is copied.
+ //! Creates a TSharedRef from TString.
+ //! Since strings are ref-counted, no data is being copied.
//! The memory is marked with a given tag.
template <class TTag>
static TSharedRef FromString(TString str);
- //! Creates a TSharedRef from a string.
- //! Since strings are ref-counted, no data is copied.
- //! The memory is marked with TDefaultSharedBlobTag.
+ //! Same as above but the memory is marked with TDefaultSharedBlobTag.
static TSharedRef FromString(TString str);
- //! Creates a TSharedRef reference from a string.
- //! Since strings are ref-counted, no data is copied.
- //! The memory is marked with a given tag.
+ //! Same as above but the memory tag is specified in #tagCookie.
static TSharedRef FromString(TString str, TRefCountedTypeCookie tagCookie);
+ //! Creates a TSharedRef from std::string.
+ //! No data is being copied in #FromString itself but since #str is passed by value
+ //! a copy may occur at caller's side.
+ //! The memory is marked with a given tag.
+ template <class TTag>
+ static TSharedRef FromString(std::string str);
+
+ //! Same as above but the memory is marked with TDefaultSharedBlobTag.
+ static TSharedRef FromString(std::string str);
+
+ //! Same as above but the memory tag is specified in #tagCookie.
+ static TSharedRef FromString(std::string str, TRefCountedTypeCookie tagCookie);
+
+ //! Creates a TSharedRef from a zero-terminated C string.
+ static TSharedRef FromString(const char* str);
+
//! Creates a TSharedRef for a given blob taking ownership of its content.
static TSharedRef FromBlob(TBlob&& blob);
@@ -176,6 +188,9 @@ public:
private:
friend class TSharedRefArrayImpl;
+
+ template <class TString>
+ static TSharedRef FromStringImpl(TString str, TRefCountedTypeCookie tagCookie);
};
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/misc/tag_invoke.h b/library/cpp/yt/misc/tag_invoke.h
new file mode 100644
index 00000000000..94cec9b295d
--- /dev/null
+++ b/library/cpp/yt/misc/tag_invoke.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include "concepts.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NTagInvokeDetail {
+
+// This name shadows possible overloads of TagInvoke from parent namespaces
+// which could be found by normal unqualified name lookup.
+void TagInvoke() = delete;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): We do not use trailing return type there in order to
+// allow incomplete tag types to be safely used here.
+struct TFn
+{
+ template <class TTag, class... TArgs>
+ requires requires (TTag&& tag, TArgs&&... args) {
+ // "Adl finds TagInvoke overload".
+ TagInvoke(std::forward<TTag>(tag), std::forward<TArgs>(args)...);
+ }
+ constexpr decltype(auto) operator()(TTag&& tag, TArgs&&... args) const
+ noexcept(noexcept(TagInvoke(std::forward<TTag>(tag), std::forward<TArgs>(args)...)))
+ {
+ return TagInvoke(std::forward<TTag>(tag), std::forward<TArgs>(args)...);
+ }
+};
+
+} // namespace NTagInvokeDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Inline namespace is required so that there is no conflict with
+// customizations of TagInvoke defined as friend function definition
+// inside a class from namespace NYT.
+inline namespace NTagInvokeCPO {
+
+inline constexpr NTagInvokeDetail::TFn TagInvoke = {};
+
+} // inline namespace NTagInvokeCPO
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Some helpful concepts and aliases.
+template <class TTag, class... TArgs>
+concept CTagInvocable = requires (TTag&& tag, TArgs&&... args) {
+ NYT::TagInvoke(std::forward<TTag>(tag), std::forward<TArgs>(args)...);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TTag, class... TArgs>
+concept CNothrowTagInvocable =
+ CTagInvocable<TTag, TArgs...> &&
+ requires (TTag&& tag, TArgs&&... args) {
+ { NYT::TagInvoke(std::forward<TTag>(tag), std::forward<TArgs>(args)...) } noexcept;
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TTag, class... TArgs>
+using TTagInvokeResult = std::invoke_result_t<decltype(NYT::TagInvoke), TTag, TArgs...>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <auto V>
+using TTagInvokeTag = std::decay_t<decltype(V)>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class TTag, class TSignature>
+struct TTagInvokeTraitsHelper;
+
+template <class TTag, class TReturn, bool NoExcept, class... TArgs>
+struct TTagInvokeTraitsHelper<TTag, TReturn(TArgs...) noexcept(NoExcept)>
+{
+ static constexpr bool IsInvocable = NYT::CInvocable<decltype(NYT::TagInvoke), TReturn(TTag, TArgs...) noexcept(NoExcept)>;
+};
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TTag, class TSignature>
+concept CTagInvocableS = NDetail::TTagInvokeTraitsHelper<TTag, TSignature>::IsInvocable;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/misc/tag_invoke_cpo.h b/library/cpp/yt/misc/tag_invoke_cpo.h
new file mode 100644
index 00000000000..9526df8adc0
--- /dev/null
+++ b/library/cpp/yt/misc/tag_invoke_cpo.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "tag_invoke.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// CRTP-base which defines a baseline CPO behavior e.g.
+// "If there is TagInvoke overload with the correct tag, use it".
+template <class TThis>
+struct TTagInvokeCpoBase
+{
+ template <class... TArgs>
+ requires CTagInvocable<const TThis&, TArgs...>
+ constexpr decltype(auto) operator()(TArgs&&... args) const
+ noexcept(CNothrowTagInvocable<const TThis&, TArgs...>)
+ {
+ return NYT::TagInvoke(static_cast<const TThis&>(*this), std::forward<TArgs>(args)...);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/misc/unittests/tag_invoke_cpo_ut.cpp b/library/cpp/yt/misc/unittests/tag_invoke_cpo_ut.cpp
new file mode 100644
index 00000000000..d7ae073c89f
--- /dev/null
+++ b/library/cpp/yt/misc/unittests/tag_invoke_cpo_ut.cpp
@@ -0,0 +1,107 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/misc/tag_invoke.h>
+#include <library/cpp/yt/misc/tag_invoke_cpo.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr struct TFooFn
+{
+
+ // Customizable overload.
+ template <class... TArgs>
+ requires CTagInvocable<TFooFn, TArgs...>
+ constexpr decltype(auto) operator() (TArgs&&... args) const
+ noexcept(noexcept(NYT::TagInvoke(*this, std::forward<TArgs>(args)...)))
+ {
+ return NYT::TagInvoke(*this, std::forward<TArgs>(args)...);
+ }
+
+ // Default overload.
+ template <class... TArgs>
+ requires (!CTagInvocable<TFooFn, TArgs...>)
+ constexpr decltype(auto) operator() (TArgs&&...) const
+ noexcept
+ {
+ return 42;
+ }
+} Foo = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTagInvokeUsageTests, DefaultOverload)
+{
+ EXPECT_EQ(Foo(42), 42);
+
+ struct TTTT
+ { };
+
+ EXPECT_EQ(Foo(TTTT{}), 42);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool NoExcept>
+struct TCustomFoo
+{
+ int Val;
+
+ friend int TagInvoke(TTagInvokeTag<Foo>, TCustomFoo f) noexcept(NoExcept)
+ {
+ return f.Val + 11;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTagInvokeUsageTests, CustomOverload)
+{
+ static_assert(CTagInvocable<TTagInvokeTag<Foo>, TCustomFoo<true>>);
+ static_assert(CTagInvocable<TTagInvokeTag<Foo>, TCustomFoo<false>>);
+ static_assert(CNothrowTagInvocable<TTagInvokeTag<Foo>, TCustomFoo<true>>);
+ static_assert(!CNothrowTagInvocable<TTagInvokeTag<Foo>, TCustomFoo<false>>);
+
+ EXPECT_EQ(Foo(TCustomFoo<true>{.Val = 42}), 53);
+ EXPECT_EQ(Foo(TCustomFoo<false>{.Val = 42}), 53);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr struct TBarFn
+ : public TTagInvokeCpoBase<TBarFn>
+{ } Bar = {};
+
+template <class T>
+concept CBarable = requires (T&& t) {
+ Bar(t);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct THasCustom
+{
+ friend int TagInvoke(TTagInvokeTag<Bar>, THasCustom)
+ {
+ return 11;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTagInvokeCpoTests, JustWorks)
+{
+ struct TNoCustom
+ { };
+ static_assert(!CBarable<TNoCustom>);
+
+ static_assert(CBarable<THasCustom>);
+ EXPECT_EQ(Bar(THasCustom{}), 11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp b/library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp
new file mode 100644
index 00000000000..e3ac0b58f25
--- /dev/null
+++ b/library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp
@@ -0,0 +1,72 @@
+////////////////////////////////////////////////////////////////////////////////
+// NB(arkady-e1ppa): This decl/def order is intentional to simulate some tricky
+// patterns of inclusion order.
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This overload should be visible by direct call
+// from namespaces enclosing ns NYT
+// but invisible to any template code
+// therefore it must not affect CTagInvocable concept.
+template <class T, class U>
+int TagInvoke(const T&, const U&)
+{
+ return 42;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): Weird name to avoid ODR violation while keeping the
+// struct inside of ns NYT (and not some anonymous ones).
+struct TUniquelyTaggedForTagInvokeImplUt
+{
+ friend int TagInvoke(TUniquelyTaggedForTagInvokeImplUt, int v)
+ {
+ return v + 2;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Actual includes start.
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/misc/tag_invoke.h>
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TTagInvokeCompileTests, UnqualidTemplate)
+{
+ static_assert(!CTagInvocable<int, int>);
+ // Unqualified finds overload defined above
+ // and never the CPO since constraints fail.
+ EXPECT_EQ(TagInvoke(42, 42), 42);
+}
+
+TEST(TTagInvokeCompileTests, HiddenFriend)
+{
+ static_assert(CTagInvocable<TUniquelyTaggedForTagInvokeImplUt, int>);
+ EXPECT_EQ(TagInvoke(TUniquelyTaggedForTagInvokeImplUt{}, 42), 44);
+ EXPECT_EQ(NYT::TagInvoke(TUniquelyTaggedForTagInvokeImplUt{}, 42), 44);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/misc/unittests/ya.make b/library/cpp/yt/misc/unittests/ya.make
index 63df71fb832..a414c5c8e53 100644
--- a/library/cpp/yt/misc/unittests/ya.make
+++ b/library/cpp/yt/misc/unittests/ya.make
@@ -11,6 +11,8 @@ SRCS(
non_null_ptr_ut.cpp
preprocessor_ut.cpp
strong_typedef_ut.cpp
+ tag_invoke_cpo_ut.cpp
+ tag_invoke_impl_ut.cpp
)
PEERDIR(