跳过正文

2025-03-21-微软的Proxy学习

·9094 字·19 分钟
作者
菜狗
Focus
目录

微软的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满足什么行为)。

    相应的解释请看

  • 让我们再看另一个例子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 parameter true). 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 模式的典型特征包括:

  1. 简化接口:通过提供一个简单的接口来隐藏系统的复杂性,从而减少使用者与系统之间的交互复杂度。
  2. 分离代码:将客户端与复杂的类库或 API 分离,使客户端代码更简洁,减少对外部复杂系统的依赖。
  3. 提高可维护性:通过引入 Facade,可以在不影响客户端的情况下更改子系统。
  4. 降低耦合度:客户端与子系统的耦合度降低,因为它们通过 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,

看看源码
#

看看这个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:

  1. composite_meta: Combines multiple invocation metadata into a single structure
  2. invocation_meta: Contains dispatch information for specific method signatures
  3. meta_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
    
  • 因为拍平了,所以调用具体的元素起来就会方便不少。

  • 避免二义性,避免因为不同层级的元素具备有同名成员,可能引发的二义性错误。

总结,好处:

  1. 扁平化结构:无论输入类型是否嵌套,最终结果均为单层继承。(看到下面就明白了为啥必须得扁平化了,因为有继承自composite_meta_impl)
  2. 统一处理逻辑:无论是单个类型还是复合类型,均通过同一套规则合并。
  3. 可扩展性:若未来需要修改合并逻辑(例如过滤某些类型),只需调整 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::template get_ptr<P, Q>会根据P的类型来生成真正的cov_disapatcher。总之how does this shit work?

这里还需要理解下,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?

  1. 每个proxy<F>对象都有details::meta_ptr<typename Traits::meta> meta; 这个meta_ptr,实际上就是使用meta_ptr_indirect_impl或者meta_ptr_direct_impl包裹的_Traits::meta。

  2. _Traits::meta是啥?对应于facade_traits<F>,使用composite_meta(composite_meta_impl)包裹(继承)了facade_traits::conv_meta。这个东西间接藏着invocation_meta

  3. 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...>;
    
  4. 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> {};
    
  5. 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>>...>;
    
  6. 绕了着么一大圈,让我们来总结下到底这个invocation_meta怎么存储的。怎么找到的对应的dispatcher?

    1. meta_ptr存储了一个meta类型,这里我们假设初始化的时候传递的是P类型
    2. 这个meta类型是将F::convention_types中的每个类型和F当成模板参数传递给conv_traits,然后将每个这个类型的meta作为模板参数,让composite_meta_impl继承。
    3. conv_traits实际上是调用了conv_traits_impl<F::convention_types中的某个类型,F,typename F::convention_types中的某个类型::overload_types>。C实际上就是convention_types的简写
    4. 总结就是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:

  1. You define a facade with a ToString operation:

    struct StringableFacade : pro::facade_builder  
        ::add_convention<FreeToString, std::string() const>  
        ::build {};
    
  2. You create a proxy with an integer:

    pro::proxy<StringableFacade> p = 42;
    
  3. During creation, the initialize method sets up the metadata with std::in_place_type<int>.

  4. Each invocation_meta in the metadata is constructed with std::in_place_type<int>, which calls get_dispatcher to get the appropriate dispatcher function for integers.

  5. The get_dispatcher method returns a pointer to conv_dispatcher<StringableFacade, IsDirect, FreeToString, int, Q, std::string>.

  6. When you call ToString(*p), the call is routed to proxy_invoke。这里实际上先调用了获取了真正的meta。然后拿了刚才初始化的时候有传递P的dispatcher函数。就是invocation_meta<F, IsDirect, D, O>::dispatcher

    struct proxy_helper {
      static inline const auto& get_meta(const proxy<F>& p) noexcept {
        assert(p.has_value());
        return *p.meta_.operator->();
      }
    
  7. proxy_invoke retrieves the metadata and finds the invocation_meta for the ToString operation.

  8. It gets the dispatcher function pointer and calls it with the proxy and arguments.

  9. The dispatcher calls std::to_string(42) to get the result.

结尾
#

唉,尴尬

狗头的赞赏码.jpg