微软的Proxy学习 #
这几天看下https://github.com/microsoft/proxy,学习下如何避免使用继承的情况下,高效方便地使用C++多态( polymorphic programming)的方法。先对着README.md看看怎么阅读这个源代码(实际上就是翻译了一下README.md)。
用法展示 #
怎么使用Proxy库?这里不展示整个的用法了,只把抽象那块的东西抽出来:
最关健的用法如下所示,简单来说就是需要组合一些行为(表达式expression)构建出来具体的外观模式
-
// 定义一个facade type: Formattable, struct Formattable : pro::facade_builder ... ::build {}
具体的pro::facade_builder请参考https://microsoft.github.io/proxy/docs/ProFacade.html,是Proxy库提供的运行时抽象(这里稍微动一下脑子理解下,代码是compile time的时候制定了runtime满足什么行为)。
相应的解释请看
pro::facade_builder
: 提供在编译时构建外观类型(模式)的能力。support_format
: 相当于concept的概念,支持某种功能 (via standard formatting functions).build
: 将上下文构建为具体的facade模式
-
让我们再看另一个例子
struct Streamable : pro::facade_builder ... ::build {}
: 定义一个Streamable
外观模式,相应的功能为pro::facade_builder
: 准备定义另一种外观模式.add_convention
: 添加调用约定,调用约定由dispatch和overload构成。这里说起来有一些拗口:什么是dispatch?什么是overload?overload好理解,可以理解为函数签名,就是具体函数的形式。dispatch呢?这里我个人认为就是功能,名字,用法之类的抽象。这里我觉得简单理解为concept就可以了pro::operator_dispatch
<"<<", true>
: Specifies a dispatch for operator<<
expressions where the primary operand (proxy
) is on the right-hand side (specified by the second template parametertrue
). Note that polymorphism in the “Proxy” library is defined by expressions rather than member functions, which is different from C++ virtual functions or other OOP languages.std::ostream&(std::ostream& out) const
: 调用约定里面的overload,函数签名部分,和std::move_only_function
. 一样的。const
指明这个函数是const
的,不会改动对象内部.build
: 将上下文构建为具体的facade模式.
-
.
如果不清楚到底什么是facade type,可以参考这个解释:
“Facade” 是一种软件设计模式,通常被称为 “外观模式”。这种设计模式提供了一个更高层次的接口,用于简化复杂系统的使用。Facade 模式的核心思想是为子系统中的一组接口提供一个统一的接口,从而让这个子系统更容易使用。
在软件开发中,Facade 模式的典型特征包括:
- 简化接口:通过提供一个简单的接口来隐藏系统的复杂性,从而减少使用者与系统之间的交互复杂度。
- 分离代码:将客户端与复杂的类库或 API 分离,使客户端代码更简洁,减少对外部复杂系统的依赖。
- 提高可维护性:通过引入 Facade,可以在不影响客户端的情况下更改子系统。
- 降低耦合度:客户端与子系统的耦合度降低,因为它们通过 Facade 进行交互,而不是直接依赖子系统的具体实现。
Facade 模式常用于提供简单的接口来处理与库、框架或一组复杂类的交互,是一种结构型设计模式,有助于提高系统的模块化和可维护性。
除了刚才提到的一些,还有一些其他有用的feature
- 重载:
facade_builder::add_convention
比上面展示的两个例子功能更强大。它可以接受任意数量的重载类型(严格来说,任何满足ProOverloadhttps://microsoft.github.io/proxy/docs/ProOverload.html要求的类型),在调用proxy时执行对函数执行重载解析。 - 外观模式组合: facade_builder::add_facade允许不同抽象的灵活组合。
- 概念:为了便于使用“proxy”进行模板编程,从facade类型中导出了三个概念。即,proxiable、proxiable_target和inplace_proxiable_target。
- 分配器感知: 函数模板allocate_proxy具备从任何自定义分配器的值创建一个proxy对象的能力。在 C++11 中,std::function和std::packaged_task的构造函数接受指定自定义分配器的功能以进行性能调优,但在 C++17 中这些被移除,因为“语义不明确,并且在未存储类型信息的上下文中,后续复制赋值期间如何再次拿到类型信息的技术问题”。这些问题不适用于allocate_proxy
- 可配置的约束: facade_builder为约束配置提供全面支持,包括内存布局(通过restrict_layout)、可复制性(通过support_copy)、可重定位性(通过support_relocation)以及可析构性(通过support_destruction)</
- 反射:proxy支持基于类型的编译时反射,这个反射支持进行运行时查询。有关更多详细信息,请参考facade_builder::add_reflection和函数模板proxy_reflect。
- 非接管(管理)代理:尽管proxy可以像智能指针一样有效地管理对象的生命周期,但有时我们希望对其进行解引用传递再传递到非接管上下文。3.2.0 版本以来,将这种能力作为扩展实现。有关更多详细信息,请参考函数模板make_proxy_view
- 运行时类型信息(RTTI):运行时类型信息(RTTI,run-time-type-information)自上世纪以来在 C++中提供了“较弱”的反射能力。虽然它不像其他一些语言中的反射那么强大(例如 C#中的 Object.GetType()或 Java 中的 Object.getClass()),但它在运行时为类型安全地强制类型转换提供了基本的基础设施。自 3.2 版本以来,“针对proxy的运行时类型信息”已作为扩展实现,并允许用户为每个外观模式定义决定是否实现rtti。有关更多详细信息,请参考facade_builder::support_rtti。
- 共享和弱所有权:虽然proxy可以从std::shared_ptr创建,但自 3.3.0 起的拓展支持用户可以更高效地创建具有共享和弱所有权的proxy对象。更多详细信息请参考函数模板make_proxy_shared、allocate_proxy_shared、别名模板weak_proxy
- 弱dispatch:当一个对象未实现调用约定,而我们不希望它构建的时候直接触发编译错误导致构建失败时,可以指定一个在调用时抛出异常的weak_dispatch。
一些proxy支持的定义的宏
In addition to the operator expressions demonstrated in the previous examples, the library supports almost all forms of expressions in C++ and can make them polymorphic. Specifically,
- macro
PRO_DEF_MEM_DISPATCH
: Defines a dispatch type for member function call expressions, providing accessibility as member functions. - macro
PRO_DEF_FREE_DISPATCH
: Defines a dispatch type for free function call expressions, providing accessibility as free functions. - macro
PRO_DEF_FREE_AS_MEM_DISPATCH
: Defines a dispatch type for free function call expressions, providing accessibility as member functions. - class template
pro::operator_dispatch
: Dispatch type for operator expressions. - class
explicit_conversion_dispatch
(aka.conversion_dispatch
) and classimplicit_conversion_dispatch
: Dispatch type for conversion expressions.
看看源码 #
看看这个https://zhuanlan.zhihu.com/p/22307747744,理解一些基础概念
直接阅读https://deepwiki.com/microsoft/proxy,岂不是更妙?
想解释怎么调用到的具体函数,就是dispatch怎么实现的?
简答来说就是facade类型用来添加具体的关键用法(加met)a,proxy包裹facade,作为容器(pro::proxy<Facade>)
三个关键的数据结构,注意这几个看完了,最后才能全体拼起来。拼起来之前先看几个helper的函数
template <class F>
struct proxy_helper {
// get meta
static inline const auto& get_meta(const proxy<F>& p) noexcept {
assert(p.has_value());
return *p.meta_.operator->();
}
// reset meta
static inline void reset_meta(proxy<F>& p) noexcept { p.meta_.reset(); }
// get_ptr是做类型转换的关键,类型转换调用就是在这里实现的
template <class P, qualifier_type Q>
static add_qualifier_t<P, Q> get_ptr(add_qualifier_t<proxy<F>, Q> p) {
if constexpr (std::is_same_v<P, proxy<F>>) {
return std::forward<add_qualifier_t<P, Q>>(p);
} else {
return static_cast<add_qualifier_t<P, Q>>(
*std::launder(reinterpret_cast<add_qualifier_ptr_t<P, Q>>(p.ptr_)));
}
}
};
接下来看下三个关键的数据结构
The invocation system uses:
composite_meta
: Combines multiple invocation metadata into a single structureinvocation_meta
: Contains dispatch information for specific method signaturesmeta_ptr
: A pointer to compile-time generated metadata about implementations。报在pro::proxy<Facade>里面,
按照上面的顺序,来看看到底这三个是什么东西,先看composite_meta。这个需要先看这几个的定义
// 可以理解为type_identity<T>就是类型T(就是别名),只不过编译器不会试图主动优先推导被std::type_identity_t<T>所遮蔽的类型,而优先推导T为何种类型,然后看看带入能不能合适(再推导std::type_identity_t<T>代表何种类型),从而避免了冲突
std::type_identity
// 类型定义的终点,实际上对应于迭代的终点,这里recursive_reduction::就是类型O
template <template <class, class> class R, class O, class... Is>
struct recursive_reduction : std::type_identity<O> {};
// 从这里就可以理解了,R是template template,这里recursive_reduction会不断地将O和I类型传递到R里面,并且新生成的O是调用的结果。Is...里面再去掉刚才床底的I
template <template <class, class> class R, class O, class I, class... Is>
struct recursive_reduction<R, O, I, Is...>
: recursive_reduction<R, R<O, I>, Is...> {};
// 实际上是个简写,对应于上面的recursive_reduction最终的类型
template <template <class, class> class R, class O, class... Is>
using recursive_reduction_t = typename recursive_reduction<R, O, Is...>::type;
// 可以理解为萃取,固定了template template R和参数包Args以后,只需要传递O & I就能调用了,算个快捷写法
template <template <class...> class R, class... Args>
struct reduction_traits {
template <class O, class I>
using type = typename R<Args..., O, I>::type;
};
依赖的东西搞完了,看看怎么组成的composite_meta
// 原地构建,实际上
in_place_type_t
template <class... Ms>
struct composite_meta_impl : Ms... {
// composite_meta_impl的默认构造啥也没干
constexpr composite_meta_impl() noexcept = default;
// composite_meta_impl实际上是让继承的参数包(Ms...)的每个构造函数都对着P做原地构建
template <class P>
constexpr explicit composite_meta_impl(std::in_place_type_t<P>) noexcept
: Ms(std::in_place_type<P>)... {}
};
// meta_reduction对于两个类型的情况,实际上是前一个类型
template <class O, class I> struct meta_reduction : std::type_identity<O> {};
// meta_reduction如果第一个参数命中传递composite_meta_impl<Ms...>,且第二个参数不为空,实际上就是composite_meta_impl<Ms..., I>类型
template <class... Ms, class I> requires(!std::is_void_v<I>)
struct meta_reduction<composite_meta_impl<Ms...>, I>
: std::type_identity<composite_meta_impl<Ms..., I>> {};
// 两个都是composite_meta_impl,那么就合并为一个composite_meta_impl
template <class... Ms1, class... Ms2>
struct meta_reduction<composite_meta_impl<Ms1...>, composite_meta_impl<Ms2...>>
: std::type_identity<composite_meta_impl<Ms1..., Ms2...>> {};
// 这里我们就拿到了composite_meta的定义了简单解释下面的含义就是,对于传递的参数包... Ms,使用
// 1 先看reduction_traits<meta_reduction>::template type,对应于reduction_traits只传递了template template R,还缺两个类型。
// 2 recursive_reduction_t调用的三个参数,分别对应R, O, Is...。其中reduction_traits<meta_reduction>::template type对应于需要传递两个模板参数的template <class, class> class R,O就是composite_meta_impl<>,而Ms...就是Is... 。所以
// 3 这段composite_meta就是对Ms不断地调用meta_reduction,拼接为一个composite_meta_impl继承Ms...,而且把每个函数拍扁
template <class... Ms>
using composite_meta = recursive_reduction_t<reduction_traits<
meta_reduction>::template type, composite_meta_impl<>, Ms...>;
看上面的代码的时候,我实际上有个问题,干嘛非得绕着么一大圈,不直接让composite_meta_impl继承所有的Ms…完事呢?
有几个点:
-
如果直接继承
Ms...
,当Ms...
中包含嵌套的composite_meta_impl
时,会导致多层继承结构。例如:using Inner = composite_meta_impl<B, C>; using Outer = composite_meta_impl<A, Inner, D>;
此时,
Outer
的基类结构是A, Inner, D
,而Inner
的基类又是B, C
。这会形成嵌套结构:Outer -> A -> Inner -> B -> C -> D
但通过
meta_reduction
和递归折叠,代码会将Inner
展开,最终生成:composite_meta_impl<A, B, C, D>
基类直接继承所有类型,结构扁平化:
Outer -> A -> B -> C -> D
-
因为拍平了,所以调用具体的元素起来就会方便不少。
-
避免二义性,避免因为不同层级的元素具备有同名成员,可能引发的二义性错误。
总结,好处:
- 扁平化结构:无论输入类型是否嵌套,最终结果均为单层继承。(看到下面就明白了为啥必须得扁平化了,因为有继承自composite_meta_impl)
- 统一处理逻辑:无论是单个类型还是复合类型,均通过同一套规则合并。
- 可扩展性:若未来需要修改合并逻辑(例如过滤某些类型),只需调整
meta_reduction
的特化规则,无需改动外部代码。
invocation_meta
invocation_meta只有一个dispatcher_type,这个里面就会看到我们是需要传递一个P的类型的,剩下的几个模板参数,比方说F,IsDirect,D,O实际上都是invocation_meta构造时就确定了的。
The dispatcher
field is initialized with a function pointer returned by overload_traits<O>::template get_dispatcher<F, IsDirect, D, P>()
. This function pointer is specific to:
- The facade type
F
- Whether the operation is direct or indirect (
IsDirect
) - The dispatch type
D
- The actual type
P
stored in the proxy
而使用不同的ptr初始化的时候,proxy_helper
这里还需要理解下,O是什么?output
template <class F, bool IsDirect, class D, class O>
struct invocation_meta {
constexpr invocation_meta() noexcept : dispatcher(nullptr) {}
//通过 std::in_place_type_t<P> 构造时,从 overload_traits<O> 获取特定分发器。
template <class P>
constexpr explicit invocation_meta(std::in_place_type_t<P>) noexcept
: dispatcher(overload_traits<O>
::template get_dispatcher<F, IsDirect, D, P>()) {}
typename overload_traits<O>::template dispatcher_type<F> dispatcher;
};
template <class O> struct overload_traits : inapplicable_traits {};
template <qualifier_type Q, bool NE, class R, class... Args>
struct overload_traits_impl : applicable_traits {
using return_type = R;
template <class F>
using dispatcher_type =
R (*)(add_qualifier_t<proxy<F>, Q>, Args...) noexcept(NE);
template <class F, bool IsDirect, class D, class P>
static consteval bool is_applicable_ptr() {
if constexpr (invocable_dispatch_ptr<IsDirect, D, P, Q, NE, R, Args...>) {
return true;
} else {
return invocable_dispatch_ptr<IsDirect, D, proxy<F>, Q, NE, R, Args...>;
}
}
template <class F, bool IsDirect, class D, class P>
static consteval dispatcher_type<F> get_dispatcher() {
if constexpr (invocable_dispatch_ptr<IsDirect, D, P, Q, NE, R, Args...>) {
return &conv_dispatcher<F, IsDirect, D, P, Q, R, Args...>;
} else {
return &conv_dispatcher<F, IsDirect, D, proxy<F>, Q, R, Args...>;
}
}
static constexpr qualifier_type qualifier = Q;
};
meta_ptr,这段就不再多解释了,简单来说就是:
- metadata比较大,meta_ptr实际上是
meta_ptr_indirect_impl
- Uses indirect storage with a pointer to static metadata - metadata够小,meta_ptr实际上是
meta_ptr_direct_impl
- Embeds the metadata directly in the proxy
所以实际上meta_ptr保存了metadata,要么用static对象,要么用原地存储
using ptr_prototype = void*[2];
// 非原地元素
template <class M>
struct meta_ptr_indirect_impl {
// 默认构造
constexpr meta_ptr_indirect_impl() noexcept : ptr_(nullptr) {};
template <class P>
constexpr explicit meta_ptr_indirect_impl(std::in_place_type_t<P>) noexcept
: ptr_(&storage<P>) {}
bool has_value() const noexcept { return ptr_ != nullptr; }
void reset() noexcept { ptr_ = nullptr; }
const M* operator->() const noexcept { return ptr_; }
private:
// 指向真正的元素
const M* ptr_;
template <class P> static constexpr M storage{std::in_place_type<P>};
};
template <class M, class DM>
struct meta_ptr_direct_impl : private M {
using M::M;
bool has_value() const noexcept { return this->DM::dispatcher != nullptr; }
void reset() noexcept { this->DM::dispatcher = nullptr; }
const M* operator->() const noexcept { return this; }
};
template <class M>
struct meta_ptr_traits_impl : std::type_identity<meta_ptr_indirect_impl<M>> {};
template <class F, bool IsDirect, class D, class O, class... Ms>
struct meta_ptr_traits_impl<
composite_meta_impl<invocation_meta<F, IsDirect, D, O>, Ms...>>
: std::type_identity<meta_ptr_direct_impl<composite_meta_impl<
invocation_meta<F, IsDirect, D, O>, Ms...>,
invocation_meta<F, IsDirect, D, O>>> {};
template <class M>
struct meta_ptr_traits : std::type_identity<meta_ptr_indirect_impl<M>> {};
template <class M>
requires(sizeof(M) <= sizeof(ptr_prototype) &&
alignof(M) <= alignof(ptr_prototype) &&
std::is_nothrow_default_constructible_v<M> &&
std::is_trivially_copyable_v<M>)
struct meta_ptr_traits<M> : meta_ptr_traits_impl<M> {};
template <class M> using meta_ptr = typename meta_ptr_traits<M>::type;
然后在看一个基础类,这段的作用说白了是将某个模板类 T
的参数列表扩展为 用户提供的参数(Args...
) + 从类型列表 TL
中提取的所有元素,从而生成最终的模板实例化类型。可以理解为实际上是个胶水。实际上又是经典的转发参数包
// 一个只声明,未定义的具体类
template <template <class...> class T, class TL, class Is, class... Args>
struct instantiated_traits;
//基础知识tuple_element_t, refer to the type of Ith element of the tuple, where I is in [0, sizeof...(Types))
// 这个实际上是个非常有意思的东西,如果传递的参数是一堆std::index_sequence<size_t数字一堆>,也就是Is是一堆size_t的时候
// 用T包裹一些参数,这些参数将Args的每个类展开在前,然后分别对TL调用std::tuple_element_t来获取对应的元素的类型
template <template <class...> class T, class TL, std::size_t... Is,
class... Args>
struct instantiated_traits<T, TL, std::index_sequence<Is...>, Args...>
: std::type_identity<T<Args..., std::tuple_element_t<Is, TL>...>> {};
// 这里就可以发现实际上是将参数和一些特定类型的Tuple展开后拼接为一起,所以这是个C++的胶水
template <template <class...> class T, class TL, class... Args>
using instantiated_t = typename instantiated_traits<
T, TL, std::make_index_sequence<std::tuple_size_v<TL>>, Args...>::type;
最后我们可以看Proxy对象了,Proxy对象这里不全部展示,只看一点,它实际上是包含了模板参数为_Traits::meta的meta_ptr,而_Traits::meta实际上是facade_traits<F>的composite_meta,初始化的时候我们传递了类型P,所以P的类型信息被放到了meta里面
这里的重点是:
- composite_meta包裹了facade_traits::conv_meta
- facade_traits::conv_meta,This means
conv_meta
is itself a composite of metadata from each convention (Cs
) that’s part of the facade.所以这里conv_meta实际上是存储了conv_traits类模板对每个类和范型类型生效的组合 - 每种conv_meta实际上是conv_traits_impl拼接为一个大的类型,并且做参数包转发,而起内里就是invocation_meta,所以可以理解为conv_traits_impl就是invocation_meta。
// meta存储了基本的类型信息
meta_ = details::meta_ptr<typename _Traits::meta>{std::in_place_type<P>};
...
details::meta_ptr<typename _Traits::meta> meta_;
alignas(F::constraints.max_align) std::byte ptr_[F::constraints.max_size];
};
struct facade_traits<F>
: instantiated_t<facade_conv_traits_impl, typename F::convention_types, F>,
instantiated_t<facade_refl_traits_impl, typename F::reflection_types, F> {
using meta = composite_meta<
lifetime_meta_t<F, copy_dispatch, void(proxy<F>&) const noexcept,
void(proxy<F>&) const, F::constraints.copyability>,
lifetime_meta_t<F, copy_dispatch, void(proxy<F>&) && noexcept,
void(proxy<F>&) &&, F::constraints.relocatability>,
lifetime_meta_t<F, destroy_dispatch, void() noexcept, void(),
F::constraints.destructibility>,
typename facade_traits::conv_meta,
typename facade_traits::refl_meta>;
...
// conv_meta的定义
struct facade_conv_traits_impl<F, Cs...> : applicable_traits {
using conv_meta = composite_meta<typename conv_traits<Cs, F>::meta...>;
// conv_traits
template <class C, class F>
struct conv_traits
: instantiated_t<conv_traits_impl, typename C::overload_types, C, F> {};
struct conv_traits_impl<C, F, Os...> : applicable_traits {
using meta = composite_meta_impl<invocation_meta<F, C::is_direct,
typename C::dispatch_type, substituted_overload_t<Os, F>>...>;
好啦,到现在我let’s put it together。问题是结构怎么存储的invocation_meta?
-
每个proxy<F>对象都有details::meta_ptr<typename Traits::meta> meta; 这个meta_ptr,实际上就是使用meta_ptr_indirect_impl或者meta_ptr_direct_impl包裹的_Traits::meta。
-
_Traits::meta是啥?对应于facade_traits<F>,使用composite_meta(composite_meta_impl)包裹(继承)了facade_traits::conv_meta。这个东西间接藏着invocation_meta
-
facade_traits::conv_meta是个什么东西?直接搜索conv_meta实际上会发现这到底是个啥?实际上是这句话,instantiated_t刚才我们看了实际上是拼接新的类型和tuple,所以这里就是说facade_traits<F>继承了facade_conv_traits_impl<F,F::convention_types>。因此facade_traits<F>的conv_meta就是facade_conv_traits_impl<F,F::convention_types>的conv_meta,就是composite_meta<typename conv_traits<Cs, F>::meta…>;,换言之,compositet_meta包裹了一堆conv_traits<Cs, F>::meta。也就是将F::convention_types中的每个类型和F当成模板参数传递给conv_traits的meta。
struct facade_traits<F> : instantiated_t<facade_conv_traits_impl, typename F::convention_types, F>, instantiated_t<facade_refl_traits_impl, typename F::reflection_types, F> ... struct facade_conv_traits_impl<F, Cs...> : applicable_traits { using conv_meta = composite_meta<typename conv_traits<Cs, F>::meta...>;
-
conv_traits<Cs, F>::meta又是什么?这里一看就发现了,实际上是conv_traits_impl<C, F, C::overload_types>。所以实际上上facade_traits<F>的conv_meta是继承自传递的composite_meta_impl继承了一堆F::convention_types里面每个元素的conv_traits_impl<C, F, C::overload_types>。接着看conv_traits_impl类型
template <class C, class F> struct conv_traits : instantiated_t<conv_traits_impl, typename C::overload_types, C, F> {};
-
conv_traits_imple我们就看到了,实际上是composite_meta_impl继承了将Os里面每个元素当成模板参数传递到invocation_meta<C::is_direct, typename C::dispatch_type, substituted_overload_t<Os, F»里面。
template <class C, class F, class... Os> requires(overload_traits<substituted_overload_t<Os, F>>::applicable && ...) struct conv_traits_impl<C, F, Os...> : applicable_traits { using meta = composite_meta_impl<invocation_meta<C::is_direct, typename C::dispatch_type, substituted_overload_t<Os, F>>...>;
-
绕了着么一大圈,让我们来总结下到底这个invocation_meta怎么存储的。怎么找到的对应的dispatcher?
- meta_ptr存储了一个meta类型,这里我们假设初始化的时候传递的是P类型
- 这个meta类型是将F::convention_types中的每个类型和F当成模板参数传递给conv_traits,然后将每个这个类型的meta作为模板参数,让composite_meta_impl继承。
- conv_traits实际上是调用了conv_traits_impl<F::convention_types中的某个类型,F,typename F::convention_types中的某个类型::overload_types>。C实际上就是convention_types的简写
- 总结就是composite_meta_impl,包裹了一堆invocation_meta带着<C::is_direct,typename C::dispatch_type, substituted_overload_t<Os, F>初始化,其中C就是F::convention_types中的某个类型,而Os就是typename F::convention_types中的某个类型::overload_types。也就是说invocation_meta构造的基本类型都可以了,而真正的对象的P实际上是meta_ptr初始化的时候作为参数传递进来的。接下来只需要看看上面的invocation_meta是什么意思就行了。invocation_meta初始化的时候给了P,而P就会作为meta存储到proxy<F>的invocation_meta里面了。
补充一个问题,invocation_meta作为被继承的类被composite_meta_impl包裹,它的内存布局里面实际上是个typename overload_traits<O>::template dispatcher_type<F> dispatcher。这个东西做了类型擦除,但是实际上存储的函数是带了P的类型的meta。意思是说Type Information is Encoded in the Dispatcher Function!即
template <class F, bool IsDirect, class D, class P, qualifier_type Q, class R, class... Args> R conv_dispatcher(add_qualifier_t<proxy<F>, Q> self, Args... args) noexcept(invocable_dispatch_ptr<IsDirect, D, P, Q, true, R, Args...>) { if constexpr (Q == qualifier_type::rv) { proxy_resetting_guard<F, P> guard{self}; return invoke_dispatch<D, R>( // 注意这里的get_ptr<P, Q>,对它而言模板参数是清楚的,所以可以在被调用之后再把P的类型给压缩回去 get_operand<IsDirect>(proxy_helper<F>::template get_ptr<P, Q>( std::move(self))), std::forward<Args>(args)...); } else { return invoke_dispatch<D, R>( get_operand<IsDirect>(proxy_helper<F>::template get_ptr<P, Q>( std::forward<add_qualifier_t<proxy<F>, Q>>(self))), std::forward<Args>(args)...); } }
runtime怎么调用?
Let’s walk through a concrete example:
-
You define a facade with a
ToString
operation:struct StringableFacade : pro::facade_builder ::add_convention<FreeToString, std::string() const> ::build {};
-
You create a proxy with an integer:
pro::proxy<StringableFacade> p = 42;
-
During creation, the
initialize
method sets up the metadata withstd::in_place_type<int>
. -
Each
invocation_meta
in the metadata is constructed withstd::in_place_type<int>
, which callsget_dispatcher
to get the appropriate dispatcher function for integers. -
The
get_dispatcher
method returns a pointer toconv_dispatcher<StringableFacade, IsDirect, FreeToString, int, Q, std::string>
. -
When you call
ToString(*p)
, the call is routed toproxy_invoke
。这里实际上先调用了获取了真正的meta。然后拿了刚才初始化的时候有传递P的dispatcher函数。就是invocation_meta<F, IsDirect, D, O>::dispatcherstruct proxy_helper { static inline const auto& get_meta(const proxy<F>& p) noexcept { assert(p.has_value()); return *p.meta_.operator->(); }
-
proxy_invoke
retrieves the metadata and finds theinvocation_meta
for theToString
operation. -
It gets the dispatcher function pointer and calls it with the proxy and arguments.
-
The dispatcher calls
std::to_string(42)
to get the result.
结尾 #
唉,尴尬