跳过正文

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

·13530 字·28 分钟
作者
菜狗
Focus
目录

微软的Proxy学习
#

这几天看下https://github.com/microsoft/proxy,学习下如何避免使用继承的情况下,高效方便地使用C++多态( polymorphic programming)的方法。先对着README.md看看怎么阅读这个源代码(实际上就是翻译了一下README.md)。

图啥?
#

看这玩意图啥?

  • CRTP,实际上是facadebuilder,就是创建facade的时候有crtp。乍一看好像和crtp没关系,因为

    struct Formattable : pro::facade_builder
        ::support<pro::skills::format>
        ::build {};
    

    (接上面)Formattable并没有把自己当成参数传递给facade_builder,但是这段里面support传递了一个pro::skills::format,这实际上是个模板类,模板参数为当前的facade_builder(FB)。所以还是递归了(奇异递归模板)

  • Compile-time constraints checking - The library uses consteval functions to validate constraints at compile time

  • Type traits and concepts - The library defines custom concepts like facade and proxiable to constrain templates

  • Method chaining with alias templates - The facade_builder uses alias templates to create a fluent interface

  • Expression templates - The library builds complex expressions at compile time through template specialization。这东西实际上就是不要中间商赚差价(不产生临时值和多次计算),直接生成最终对象

  • Pointer-based type erasure :类型擦除,看invoke_meta & invocation_meta。类型擦除的秘密实际上就是真正存储类型P的时候,会调用

      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>()) {}
    
      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...>;
        }
      }
    

    这东西实际上是把具体类型P的conv_dispatcher编出来一个新的函数,然后转换成了不同的conv_dispatcher。conv_dispatcher实际上是个函数指针,调用的时候通过不同的conv_dispatcher去找到真正的函数。换言之因为它是个函数指针,完全可以隐藏大量的信息,只要signature能匹配上就行。

  • Variadic template parameters,一堆reduction啥的,看看咋整的

  • Tag dispatching

  • SFINAE,这个知道个概念就行

用法展示
#

怎么使用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好理解,可以理解为函数签名,就是具体函数的形式,比方说std::ostream&(std::ostream& out) const。dispatch呢?这里我个人认为就是功能,名字,用法之类的抽象。更专业一些是,a dispatch type is a mechanism that defines how operations on proxy objects are forwarded to their underlying concrete implementations。 为什么拆开?因为dispatch Defines the mechanism for invoking operations (member function, free function, operator, etc.),是用来指示如何how to forward calls to the underlying object
    • 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,岂不是更妙?

概念解释
#

这里我先把一些基础概念的中文意思写出来,方便理解

F :facade, 你可以将之理解为一个concept限制,用户(研发)给了一些函数,constraint和对内存布局的要求。但它只要满足用户的这些要求,就能用proxy<F>包裹任何用户给的满足要求的不同类型的对象。 D:dispatch, 定义使用何种方式来调用相应的函数前面,比方说是operation,还是free function?还是member function?简单来说是用来指示如何how to forward calls to the underlying object Os : Overloads ,函数签名。平时可以这样说"This facade can handle operations of type X (the dispatch type) with interface Y (the function signature)." C/Cs : convention 把D和O拼起来就是一个对调用的抽象,理解为meta得了 Rs : 这个东西实际上是reflection_types,也就是反射 A/As :accessor,可以理解为每个contention定义出来以后,你对于真正对象的访问实际上都是通过这个accessor,也就是实际上你访问的它 Q : qualifier ,A *qualifier* adds an extra “quality”, 比方说const这些Cpp里面加属性的都是qualifier NE :noexcept P : proxy指向的具体的对象

一些数据结构的概念会在下面解析的过程中给出

怎么实现的调用正确的函数呀?
#

一下关键的数据结构和一些概念的意思,稍微注意一下这里写的conv_Meta和Metas和conv_Metas的区别。

不想看整个推导过程就看下面这点引用。。。

convention是怎么添加的,conv_traits是时候创建的,看下一节

Ms,理解为Metas就行了,在调用这块看的时候当成conv_Meta

proxy_helper 辅助调用或者拿meta的时候的一个结构,当成个工具类得了

composite_meta,这个东西是一个存储了所有作为参数包的conv_Metas的对象(composite_meta_impl)。是个范型,模板参数当成conv_metas得了

composite_meta_impl:一个拍扁了的meta存储的结构,这个结构最好玩的是它实际上直接拿到了最高层也就是真正的Meta,即使传递给composite_meta_impl的Metas是个composite_meta_impl对象(就是即使composite_meta_impl继承的Metas是个composite_meta_impl对象)

invocation_meta:实际上真正的conv_Meta,是这个玩意。这东西存储了facade,存储了dispatch,存储了overload。构造的时候传递了对象的真正类型P,然后就把类型P擦除了(实际上是存在了更深层的地方)。这里因为把类型P擦除掉了,所以可以在定义的时候,每个结构可以直接有一堆invocation_meta。但是使用的时候要把P就是真正类型存储进去,然后对着P调用dispatcher就行了。

meta_ptr:proxy<f>存储的实际上是这个东西,这个东西存储间接或者直接的存储了所有的metas。存储的东西实际上是个范型就是_Traits::meta

_Traits::meta:实际上是facade_traits<F>::meta,包含了life_time_meta,conv_metas,反射meta一堆东西。 因为facade_traits<F>继承了instantiated_t<facade_conv_traits_impl, typename F::convention_types, F>,就是继承std::type_identity<facade_conv_traits_impl<F, F::convention_types>。

conv_meta:上面说了facade_traits是啥,可以知道conv_metas就是composite_meta<typename conv_traits<F::convention_types, F>::meta…>;,简单来说就是composite_meta_impl继承一堆conv_traits<F::convention_type, F>::meta

conv_traits实际上在build的时候就定好了,conv_traits_impl<C, F, typename C::overload_types>

总结:proxy<F>有个meta_ptr指针,指向facade_traits<F>::meta,通过继承facade_conv_traits_impl有conv_meta属性,就是composite_meta<typename conv_traits<Cs, F>::meta…>(展开了…Cs)。里面这个conv_traits<Cs, F>::meta,就是conv_traits_impl<C, F, typename C::overload_types>。所以这个conv_traits_impl一堆东西实际上是invocation_meta<F, C::is_direct, typename C::dispatch_type, substituted_overload_t<Os, F»…。也就是继承了一堆invocation_meta对每个具体的C展开Os的情况。下面画个图。Nmmmm,我没检查细节。。。可能有错

从meta到invocation_meta

想解释怎么调用到的具体函数,就是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 的参数,就是class TL, class… Args,扩展为 T<用户提供的参数(Args...) + 从类型tuple 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];
};

// 注意看,这里facade_traits继承了,所以实际上是说facade_traits是对F的
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.

Convention怎么加,为啥有效?
#

Conventions are the fundamental building blocks that define the behaviors and capabilities a facade can exhibit.

下来看下究竟Proxy是确定Proxy<F>真正存储的对象的函数满足contentions的要求的。另外就是这么把facade_traits和真正的

先看看facade_builder实际上就是basic_facade_builder, 只不过Rs和C谁都是tuple<>,而conv_imple硬生生拆出来一堆Os…

而add_convention,实际上是创建了一个新的basic_facade_builder,这里的Os实际上已经被details::extended_overload解析过了。之后调用add_conv_t创建了一堆conv(可能会有疑问,如果signature相同,而函数名字不同,是否有冲突?实际上函数名字在dispatcher里面)

// 这里的tuple<>可以当成递归终止条件或占位符,所以真正调用add_convention的时候,这里实际上是传递的两个的默认的std::tuple<>
using facade_builder = basic_facade_builder<std::tuple<>, std::tuple<>,
    proxiable_ptr_constraints{
        .max_size = details::invalid_size,
        .max_align = details::invalid_size,
        .copyability = details::invalid_cl,
        .relocatability = details::invalid_cl,
        .destructibility = details::invalid_cl}>;

// 注意这个details::extended_overload, 已经解析过了
// template <class O>
// concept extended_overload = overload_traits<O>::applicable ||
//     overload_substitution_traits<O>::applicable;
template <class D, details::extended_overload... Os>
      requires(sizeof...(Os) > 0u)
  using add_convention = add_indirect_convention<D, Os...>;
  
// 上面说了调用的facade_builder,实际上是basic_facade_builder<std::tuple<>, std::tuple<>...
// 所以这里实际上add_indirect_convention是basic_facade_builder<
//      details::add_conv_t<std::tuple<>, details::conv_impl<false, D, Os...>>, std::tuple<>, C>;
  template <class D, details::extended_overload... Os>
      requires(sizeof...(Os) > 0u)
  using add_indirect_convention = basic_facade_builder<
      details::add_conv_t<Cs, details::conv_impl<false, D, Os...>>, Rs, C>;

// 再看一下add_conv_t,就发现又是类似void_t的特化,第一个参数默认对于tuple<>,完美命中。然后又是一套conv_reduction那套
template <class Cs, class C>
using add_conv_t = typename add_conv_reduction<std::tuple<>, Cs, C>::type;

// conv_imple的定义,一会就被送到add_conv_t里面了
template <bool IsDirect, class D, class... Os>
struct conv_impl {
  static constexpr bool is_direct = IsDirect;
  using dispatch_type = D;
  using overload_types = std::tuple<Os...>;
  template <class F>
  using accessor = instantiated_accessor_t<
      D, F, IsDirect, substituted_overload_t<Os, F>...>;
};

上面这堆解析完,实际上是调用了build,简单来说就是我们建立了一堆Os后,调用details::facade_impl<Cs, Rs, details::normalize(C)>

// 这个是只加convention的情况,如果加reflection,第二个Rs就不是tuple<>了
basic_facade_builder<
      新的Cs, std::tuple<>, C>::build。。。

// 最终得出一个,这个Cs & Rs就是新得出的东西。
details::facade_impl<Cs, Rs, details::normalize(C)>

// facade_impl就是这么个东西,convention_types实际上是个tuple
template <class Cs, class Rs, proxiable_ptr_constraints C>
struct facade_impl {
  using convention_types = Cs;
  using reflection_types = Rs;
  static constexpr proxiable_ptr_constraints constraints = C;
};
template <class R, class... Args>
struct overload_traits<R(Args...)>
    : overload_traits_impl<qualifier_type::lv, false, R, Args...> {};
template <class R, class... Args>
struct overload_traits<R(Args...) noexcept>
    : overload_traits_impl<qualifier_type::lv, true, R, Args...> {};
template <class R, class... Args>
struct overload_traits<R(Args...) &>
    : overload_traits_impl<qualifier_type::lv, false, R, Args...> {};
template <class R, class... Args>
struct overload_traits<R(Args...) & noexcept>
    : overload_traits_impl<qualifier_type::lv, true, R, Args...> {};
template <class R, class... Args>
struct overload_traits<R(Args...) &&>
    : overload_traits_impl<qualifier_type::rv, false, R, Args...> {};
template <class R, class... Args>
struct overload_traits<R(Args...) && noexcept>
    : overload_traits_impl<qualifier_type::rv, true, R, Args...> {};

到这里,facade存储了这些conventions了。

那么调用的时候又是怎么回事呢?

简单来说就是,用访访问Accessor,然后调用proxy_invoke ==> invoke_impl.

比方说

// 这里实际上没有创建accessor,指示创建了一个proxy holder
pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>(3.14159);
// 对p3做*的时候,accessor才创建
std::cout << "p3 = " << std::fixed << std::setprecision(2) << *p3 << "\n";  // Prints "p3 = 3.14"

在创建accessor的时候实际上是走了定义

关键数据结构applicable_ptr

template <class P, class F>
concept proxiable = facade<F> && details::facade_traits<F>::applicable &&
    details::facade_traits<F>::template applicable_ptr<P>;

而applicable_ptr

  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...>;
    }
  }

结尾
#

唉,尴尬

狗头的赞赏码.jpg