aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-11-08 19:15:54 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-11-08 19:28:18 +0300
commit69ea2aad0490aa3b45a377306f29d0e94876ee68 (patch)
tree31ca006abc59af483f916d4aa2651506a8ae18d1
parent29bd656ff6c7a9260405dc556cf9b56765f16eb4 (diff)
downloadydb-69ea2aad0490aa3b45a377306f29d0e94876ee68.tar.gz
YT-21233: Rewrite ConvertTo CPO using TagInvoke
Plan: 1) Remove `IAttributedDictionary` type from the public API. \+ 2) Remove `Set` method from public API in favor of `operator<<=`. \+ 3) Adopt `ConvertTo<T>` (or other name) CPO with proper extension into `NYT::NYson::ConvertTo` from `yt/core`. 4) Use CPO from (3) to eliminate direct dependency on `yt/core` of `Get/Find` methods from attributes API. 5) Adopt `ConvertToYsonString` (or other name) CPO with proper extension into `yt/core` customisations. 6) Use CPO from (5) to eliminate direct dependency on `yt/core` of `TErrorAttribute` ctor. 7) Swap attributes implementation to the one which doesn’t use `IAttributeDictionary`. 8) At this point `stripped_error*` can be moved to library/cpp/yt and so can recursively dependant on THROW macro methods `Get/Find/…`. 9) Adjust CPO’s to work with `std::string` instead of `TYsonString` assuming text format to be used (maybe `TString` for now). 10) Remove dep of `library/cpp/yt/error` on `yson` entirely. This pr addresses 3-4 steps of plan. Below is a brief explanation of design decisions. We want to have a concept which detects if there is a `ConvertTo` method and if true, try calling it. Templates can only perform unqualified name lookup and if we allow non-ADL overloads to be found, we would have dependency on inclusion order (if `ConvertTo` is included prior to our code, everything works fine, but if the order is reverse, templated dispatch would fail, but direct call would work just fine). That is why we adopt niebloids which first disable ADL lookup of the name `ConvertTo` by directing it to niebloid implemented via `TagInvoke` mechanism. TagInvoke design <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf> . TL;DR: we want to have behavior which is consistent with inclusion order. Key difference now is that `ConvertTo` works consistently in both manual calls and template function body calls and is no longer visible for ADL part of the unqualified name lookup commit_hash:32af641bd0af559bfe670c2ceb36721fb4afc2dd
-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/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.cpp106
-rw-r--r--library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp71
-rw-r--r--library/cpp/yt/misc/unittests/ya.make2
-rw-r--r--yt/yt/core/logging/file_log_writer.cpp2
-rw-r--r--yt/yt/core/misc/stripped_error.cpp11
-rw-r--r--yt/yt/core/ytree/attributes-inl.h35
-rw-r--r--yt/yt/core/ytree/convert-inl.h153
-rw-r--r--yt/yt/core/ytree/convert.h11
-rw-r--r--yt/yt/core/ytree/node-inl.h8
14 files changed, 493 insertions, 113 deletions
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 0000000000..55f608c3b6
--- /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 a05e62d0e9..8ffbf3e0a3 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 88771f8360..7c2f4a5407 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/misc/tag_invoke.h b/library/cpp/yt/misc/tag_invoke.h
new file mode 100644
index 0000000000..94cec9b295
--- /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 0000000000..9526df8adc
--- /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 0000000000..3667dbc033
--- /dev/null
+++ b/library/cpp/yt/misc/unittests/tag_invoke_cpo_ut.cpp
@@ -0,0 +1,106 @@
+#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 0000000000..47b8e0480d
--- /dev/null
+++ b/library/cpp/yt/misc/unittests/tag_invoke_impl_ut.cpp
@@ -0,0 +1,71 @@
+////////////////////////////////////////////////////////////////////////////////
+// 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 63df71fb83..a414c5c8e5 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(
diff --git a/yt/yt/core/logging/file_log_writer.cpp b/yt/yt/core/logging/file_log_writer.cpp
index d72c2f4e07..4e8100ca31 100644
--- a/yt/yt/core/logging/file_log_writer.cpp
+++ b/yt/yt/core/logging/file_log_writer.cpp
@@ -368,7 +368,7 @@ public:
private:
static TFileLogWriterConfigPtr ParseConfig(const NYTree::IMapNodePtr& configNode)
{
- return ConvertTo<TFileLogWriterConfigPtr>(configNode);
+ return NYT::NYTree::ConvertTo<TFileLogWriterConfigPtr>(configNode);
}
};
diff --git a/yt/yt/core/misc/stripped_error.cpp b/yt/yt/core/misc/stripped_error.cpp
index 1bb32bea82..e43a61953f 100644
--- a/yt/yt/core/misc/stripped_error.cpp
+++ b/yt/yt/core/misc/stripped_error.cpp
@@ -921,4 +921,15 @@ const char* TErrorException::what() const noexcept
////////////////////////////////////////////////////////////////////////////////
+// TODO(arkady-e1ppa): Move this out eventually.
+[[noreturn]] void TErrorAttributes::ThrowCannotParseAttributeException(TStringBuf key, const std::exception& ex)
+{
+ THROW_ERROR_EXCEPTION(
+ "Error parsing attribute %Qv",
+ key)
+ << ex;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
diff --git a/yt/yt/core/ytree/attributes-inl.h b/yt/yt/core/ytree/attributes-inl.h
index d20ed98dc7..cfb18c40cf 100644
--- a/yt/yt/core/ytree/attributes-inl.h
+++ b/yt/yt/core/ytree/attributes-inl.h
@@ -105,39 +105,4 @@ struct TMergeDictionariesTraits<NYTree::IAttributeDictionary>
////////////////////////////////////////////////////////////////////////////////
-// TODO(arkady-e1ppa): Move this out eventually.
-template <class T>
-T TErrorAttributes::Get(TStringBuf key) const
-{
- using NYTree::ConvertTo;
- auto yson = GetYson(key);
- try {
- return ConvertTo<T>(yson);
- } catch (const std::exception& ex) {
- THROW_ERROR_EXCEPTION("Error parsing attribute %Qv",
- key)
- << ex;
- }
-}
-
-template <class T>
-typename TOptionalTraits<T>::TOptional TErrorAttributes::Find(TStringBuf key) const
-{
- using NYTree::ConvertTo;
-
- auto yson = FindYson(key);
- if (!yson) {
- return typename TOptionalTraits<T>::TOptional();
- }
- try {
- return ConvertTo<T>(yson);
- } catch (const std::exception& ex) {
- THROW_ERROR_EXCEPTION("Error parsing attribute %Qv",
- key)
- << ex;
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
} // namespace NYT
diff --git a/yt/yt/core/ytree/convert-inl.h b/yt/yt/core/ytree/convert-inl.h
index 2deb79c6f0..694aca2d81 100644
--- a/yt/yt/core/ytree/convert-inl.h
+++ b/yt/yt/core/ytree/convert-inl.h
@@ -166,17 +166,91 @@ IAttributeDictionaryPtr ConvertToAttributes(const T& value)
////////////////////////////////////////////////////////////////////////////////
+const NYson::TToken& SkipAttributes(NYson::TTokenizer* tokenizer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+T ConstructYTreeConvertibleObject()
+{
+ if constexpr (std::is_constructible_v<T>) {
+ return T();
+ } else {
+ return T::Create();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+double ConvertYsonStringBaseToDouble(const NYson::TYsonStringBuf& yson)
+{
+ NYson::TTokenizer tokenizer(yson.AsStringBuf());
+ const auto& token = SkipAttributes(&tokenizer);
+ switch (token.GetType()) {
+ case NYson::ETokenType::Int64:
+ return token.GetInt64Value();
+ case NYson::ETokenType::Double:
+ return token.GetDoubleValue();
+ case NYson::ETokenType::Boolean:
+ return token.GetBooleanValue();
+ default:
+ THROW_ERROR_EXCEPTION("Cannot parse \"double\" from %Qlv",
+ token.GetType())
+ << TErrorAttribute("data", yson.AsStringBuf());
+ }
+}
+
+TString ConvertYsonStringBaseToString(const NYson::TYsonStringBuf& yson)
+{
+ NYson::TTokenizer tokenizer(yson.AsStringBuf());
+ const auto& token = SkipAttributes(&tokenizer);
+ switch (token.GetType()) {
+ case NYson::ETokenType::String:
+ return TString(token.GetStringValue());
+ default:
+ THROW_ERROR_EXCEPTION("Cannot parse \"string\" from %Qlv",
+ token.GetType())
+ << TErrorAttribute("data", yson.AsStringBuf());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYTree
+
+namespace NYT::NConvertToImpl {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): TTagInvokeTag uses decltype under the hood
+// meaning the resulting expression is not viable for template argument deduction
+// thus we have to write the type by hand in order to have TTo deducible
+// automatically.
template <class TTo>
-TTo ConvertTo(const INodePtr& node)
+TTo TagInvoke(NConvertToImpl::TFn<TTo>, const NYTree::INodePtr& node)
{
+ using namespace NYTree;
+
auto result = ConstructYTreeConvertibleObject<TTo>();
Deserialize(result, node);
return result;
}
+////////////////////////////////////////////////////////////////////////////////
+
template <class TTo, class TFrom>
-TTo ConvertTo(const TFrom& value)
+TTo TagInvoke(NConvertToImpl::TFn<TTo>, const TFrom& value)
{
+ using namespace NYTree;
+
auto type = GetYsonType(value);
if constexpr (
NYson::ArePullParserDeserializable<TTo>() &&
@@ -203,12 +277,13 @@ TTo ConvertTo(const TFrom& value)
return buildingConsumer->Finish();
}
-const NYson::TToken& SkipAttributes(NYson::TTokenizer* tokenizer);
+////////////////////////////////////////////////////////////////////////////////
#define IMPLEMENT_CHECKED_INTEGRAL_CONVERT_TO(type) \
template <> \
- inline type ConvertTo(const NYson::TYsonString& str) \
+ inline type TagInvoke(TTagInvokeTag<ConvertTo<type>>, const NYson::TYsonString& str) \
{ \
+ using namespace NYTree; \
NYson::TTokenizer tokenizer(str.AsStringBuf()); \
const auto& token = SkipAttributes(&tokenizer); \
switch (token.GetType()) { \
@@ -236,82 +311,30 @@ IMPLEMENT_CHECKED_INTEGRAL_CONVERT_TO(ui8)
////////////////////////////////////////////////////////////////////////////////
-template <class T>
-T ConstructYTreeConvertibleObject()
-{
- if constexpr (std::is_constructible_v<T>) {
- return T();
- } else {
- return T::Create();
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-
-////////////////////////////////////////////////////////////////////////////////
-
-double ConvertYsonStringBaseToDouble(const NYson::TYsonStringBuf& yson)
-{
- NYson::TTokenizer tokenizer(yson.AsStringBuf());
- const auto& token = SkipAttributes(&tokenizer);
- switch (token.GetType()) {
- case NYson::ETokenType::Int64:
- return token.GetInt64Value();
- case NYson::ETokenType::Double:
- return token.GetDoubleValue();
- case NYson::ETokenType::Boolean:
- return token.GetBooleanValue();
- default:
- THROW_ERROR_EXCEPTION("Cannot parse \"double\" from %Qlv",
- token.GetType())
- << TErrorAttribute("data", yson.AsStringBuf());
- }
-}
-
-TString ConvertYsonStringBaseToString(const NYson::TYsonStringBuf& yson)
-{
- NYson::TTokenizer tokenizer(yson.AsStringBuf());
- const auto& token = SkipAttributes(&tokenizer);
- switch (token.GetType()) {
- case NYson::ETokenType::String:
- return TString(token.GetStringValue());
- default:
- THROW_ERROR_EXCEPTION("Cannot parse \"string\" from %Qlv",
- token.GetType())
- << TErrorAttribute("data", yson.AsStringBuf());
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-}
-
template <>
-inline double ConvertTo(const NYson::TYsonString& str)
+inline double TagInvoke(TTagInvokeTag<ConvertTo<double>>, const NYson::TYsonString& str)
{
- return ConvertYsonStringBaseToDouble(str);
+ return NYTree::ConvertYsonStringBaseToDouble(str);
}
template <>
-inline double ConvertTo(const NYson::TYsonStringBuf& str)
+inline double TagInvoke(TTagInvokeTag<ConvertTo<double>>, const NYson::TYsonStringBuf& str)
{
- return ConvertYsonStringBaseToDouble(str);
+ return NYTree::ConvertYsonStringBaseToDouble(str);
}
template <>
-inline TString ConvertTo(const NYson::TYsonString& str)
+inline TString TagInvoke(TTagInvokeTag<ConvertTo<TString>>, const NYson::TYsonString& str)
{
- return ConvertYsonStringBaseToString(str);
+ return NYTree::ConvertYsonStringBaseToString(str);
}
template <>
-inline TString ConvertTo(const NYson::TYsonStringBuf& str)
+inline TString TagInvoke(TTagInvokeTag<ConvertTo<TString>>, const NYson::TYsonStringBuf& str)
{
- return ConvertYsonStringBaseToString(str);
+ return NYTree::ConvertYsonStringBaseToString(str);
}
////////////////////////////////////////////////////////////////////////////////
-} // namespace NYT::NYTree
+} // namespace NYT::NConvertToImpl
diff --git a/yt/yt/core/ytree/convert.h b/yt/yt/core/ytree/convert.h
index 8d21d1f8ca..d80e4448f2 100644
--- a/yt/yt/core/ytree/convert.h
+++ b/yt/yt/core/ytree/convert.h
@@ -5,6 +5,7 @@
#include <yt/yt/core/yson/consumer.h>
+#include <library/cpp/yt/error/convert_to_cpo.h>
namespace NYT::NYson {
@@ -49,17 +50,15 @@ INodePtr ConvertToNode(
template <class T>
IAttributeDictionaryPtr ConvertToAttributes(const T& value);
-template <class TTo>
-TTo ConvertTo(const INodePtr& node);
-
-template <class TTo, class TFrom>
-TTo ConvertTo(const TFrom& value);
-
template <class T>
T ConstructYTreeConvertibleObject();
////////////////////////////////////////////////////////////////////////////////
+using NYT::ConvertTo;
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT::NYTree
#define CONVERT_INL_H_
diff --git a/yt/yt/core/ytree/node-inl.h b/yt/yt/core/ytree/node-inl.h
index 562f5fdb2a..a766ad7f02 100644
--- a/yt/yt/core/ytree/node-inl.h
+++ b/yt/yt/core/ytree/node-inl.h
@@ -4,16 +4,12 @@
#include "node.h"
#endif
+#include <library/cpp/yt/error/convert_to_cpo.h>
+
namespace NYT::NYTree {
////////////////////////////////////////////////////////////////////////////////
-// Forward declaration.
-template <class TTo>
-TTo ConvertTo(const INodePtr& node);
-template <class TTo, class TFrom>
-TTo ConvertTo(const TFrom& value);
-
template <class T>
T INode::GetValue() const
{