123789456ye

已AFO

Proxy 库解析(一)

前言

我去年就有看到宣传 Proxy 库的文章。
我很喜欢模板元的做法,编译期的类型推断,但是我始终觉得基于宏的实现很丑
而且源代码有着库代码和模板元双重的丑陋,还没有注释,因此我没去仔细读
之前写了些 Rust, 对模板和分发这些理解更深刻了。突然由于各种原因有了一点闲暇时间,那就仔细读一下吧
虽然说是 Header only,虽然说只有 2k+ 行,但是模板元读起来还是令人头疼,因此本篇会借助 GPT 来解释
本篇基于 Proxy v4.0.0.
p.s. 这个大版本似乎加了Wiki,一看搞得这个系列有点小丑

命名约定

基本上来说,T 代表 Template/Type,R 代表 Reduction,O 代表 Origin/Output,I 代表 Input,P 代表类型,F 代表 Function/Facade
后缀 s 则代表可变参数包

辅助工具

类型推断

template <template <class, class> class R, class O, class... Is>
struct recursive_reduction : std::type_identity<O> {};
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...> {};
template <template <class, class> class R, class O, class... Is>
using recursive_reduction_t = typename recursive_reduction<R, O, Is...>::type;

recursive_reduction_t 当我们有一个二元操作符 R,初值 O,入参序列 Is 时,我们不断将 O 换成 R<O, I> ,最终返回 reduce 完成后的 type

template <template <class...> class R, class... Args>
struct reduction_traits {
  template <class O, class I>
  using type = typename R<Args..., O, I>::type;
};

reduction_traits 在固定前缀 Args 后,追加 O 和 I,再输入 R 计算
R 是接受可变参数包的,然而我们在这里需要把其降为二元操作

template <class O, class I>
struct composition_reduction : std::type_identity<O> {};
template <template <class...> class T, class... Os, class I>
  requires(!std::is_void_v<I>)
struct composition_reduction<T<Os...>, I> : std::type_identity<T<Os..., I>> {};
template <template <class...> class T, class... Os, class... Is>
struct composition_reduction<T<Os...>, T<Is...>>
    : std::type_identity<T<Os..., Is...>> {};

composition_reduction 这个没什么好说的,可以拼一个,也可以拼相同模板的两个序列,例如tuple和tuple
这个requires的作用是跳过 void
为什么我们可以这样写这个requires?请看下一行

template <class T, class... Us>
using composite_t = recursive_reduction_t<
    reduction_traits<composition_reduction>::template type, T, Us...>;

composite_t简单来说就是调用composite_reduction逐个合并序列,如果碰到了 void 就会退化到第一个偏特化上,也就是跳过

template <class Expr>
consteval bool is_consteval(Expr) {
  return requires { typename std::bool_constant<(Expr{}(), false)>; };
}

相当tricky的写法,requires 本身其实也能返回 bool 值,结合实例化检查其能否在编译期求 operator() 值

template <class T, class U>
concept static_prop = std::is_same_v<T, const U&>;

定义一个 concept 以备后续使用
有一个糖是这样的:requires { { expr } -> Concept; }会把 decltype(expr)传给 concept 的第一个参数

tuple检查和展开

template <class T, std::size_t I>
concept has_tuple_element = requires { typename std::tuple_element_t<I, T>; };
template <class T>
consteval bool is_tuple_like_well_formed() {
  if constexpr (requires {
                  { std::tuple_size<T>::value } -> static_prop<std::size_t>;
                }) {
    if constexpr (is_consteval([] { return std::tuple_size<T>::value; })) {
      return []<std::size_t... I>(std::index_sequence<I...>) {
        return (has_tuple_element<T, I> && ...);
      }(std::make_index_sequence<std::tuple_size_v<T>>{});
    }
  }
  return false;
}

has_tuple_element确保所有类型都能实例化
requires 可以理解为库代码的防御性
下面就是根据 size 遍历每一个元素,总之就是确保这个 tuple 是 well-formed

template <template <class...> class T, class TL, class Is, class... Args>
struct instantiated_traits;
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>...>> {};
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;

总之instantiated_t就是把根据模板 T 把 tuple-like 的 TL 展开,再把 Args 放在最前面

内部结构

facade

template <class F>
struct basic_facade_traits;

基本定义

template <template <class> class O>
struct facade_aware_overload_t {
  facade_aware_overload_t() = delete;
};

template <class F>
concept facade = details::basic_facade_traits<F>::applicable;

定义 concept facade

template <facade F>
class proxy_indirect_accessor;
template <facade F>
class proxy;

重点之一, indirect_accessor 和 proxy 定义

template <class T>
struct is_bitwise_trivially_relocatable
    : std::bool_constant<std::is_trivially_move_constructible_v<T> &&
                         std::is_trivially_destructible_v<T>> {};
template <class T>
constexpr bool is_bitwise_trivially_relocatable_v =
    is_bitwise_trivially_relocatable<T>::value;

字面意思,可平凡移动构造且可析构,合起来就可以 memcpy

constraint level & qualifier type

struct applicable_traits {
  static constexpr bool applicable = true;
};
struct inapplicable_traits {
  static constexpr bool applicable = false;
};

控制特化

enum class constraint_level { none, nontrivial, nothrow, trivial };
enum class qualifier_type { lv, const_lv, rv, const_rv };

然后以下就是排列组合,略去

proxy helper

struct proxy_helper {
  template <class P, class F>
  struct resetting_guard {
    explicit resetting_guard(proxy<F>& p) noexcept : p_(p) {}
    ~resetting_guard() noexcept(std::is_nothrow_destructible_v<P>) {
      std::destroy_at(std::addressof(get_ptr<P, F, qualifier_type::lv>(p_)));
      p_.meta_.reset();
    }

  private:
    proxy<F>& p_;
  };

  template <class F>
  static const auto& get_meta(const proxy<F>& p) noexcept {
    assert(p.has_value());
    return *p.meta_.operator->();
  }
  template <class P, class F, qualifier_type Q>
  static add_qualifier_t<P, Q> get_ptr(add_qualifier_t<proxy<F>, Q> p) {
    return static_cast<add_qualifier_t<P, Q>>(
        *std::launder(reinterpret_cast<add_qualifier_ptr_t<P, Q>>(p.ptr_)));
  }
  template <class P, class F1, class F2>
  static void trivially_relocate(proxy<F1>& from, proxy<F2>& to) noexcept {
    std::uninitialized_copy_n(from.ptr_, sizeof(P), to.ptr_);
    to.meta_ = decltype(proxy<F2>::meta_){std::in_place_type<P>};
    from.meta_.reset();
  }
};

重点之一的后续,这里还是比较直观的
resetting_guard确保 proxy 的 RAII, get_meta 取出 meta 信息
get_ptr取出 proxy 内部的指针,然后强转,launder 也是因为这里直接用了reinterpret_cast
trivially_relocate则是直接 memcpy,然后更新两边的 meta

invoke

template <class P, bool IsDirect, qualifier_type Q>
struct operand_traits : add_qualifier_traits<P, Q> {};
template <class P, qualifier_type Q>
struct operand_traits<P, false, Q>
    : std::type_identity<decltype(*std::declval<add_qualifier_t<P, Q>>())> {};
template <class P, bool IsDirect, qualifier_type Q>
using operand_t = typename operand_traits<P, IsDirect, Q>::type;

如果是 indirect 的话,首先 declval 得到一个 P 的右值,根据 Q 加上 qualifier,解引用,最后再 decltype
如果不是 indirect 话,直接加 qualifier 就行

template <class P, bool IsDirect, class D, qualifier_type Q, bool NE, class R,
          class... Args>
concept invocable_dispatch =
    (IsDirect || (requires { *std::declval<add_qualifier_t<P, Q>>(); } &&
                  (!NE || noexcept(*std::declval<add_qualifier_t<P, Q>>())))) &&
    ((NE && std::is_nothrow_invocable_r_v<R, D, operand_t<P, IsDirect, Q>,
                                          Args...>) ||
     (!NE &&
      std::is_invocable_r_v<R, D, operand_t<P, IsDirect, Q>, Args...>)) &&
    (Q != qualifier_type::rv || (NE && std::is_nothrow_destructible_v<P>) ||
     (!NE && std::is_destructible_v<P>));

依托,没什么好讲的,看代码,记住 concept 约等于 constexpr bool 就行

template <class D, class R, class... Args>
R invoke_dispatch_impl(Args&&... args) {
  if constexpr (std::is_void_v<R>) {
    D()(std::forward<Args>(args)...);
  } else {
    return D()(std::forward<Args>(args)...);
  }
}

分发的终点,执行 D(),特判 R 为 void 的情况

template <bool IsDirect, class P>
decltype(auto) get_operand(P&& ptr) {
  if constexpr (IsDirect) {
    return std::forward<P>(ptr);
  } else {
    if constexpr (std::is_constructible_v<bool, P&>) {
      assert(ptr);
    }
    return *std::forward<P>(ptr);
  }
}

按需解引用,拿到 operand

struct internal_dispatch {};
template <class P, class F, bool IsDirect, class D, qualifier_type Q, bool NE,
          class R, class... Args>
R invoke_dispatch(add_qualifier_t<proxy<F>, Q> self,
                  Args... args) noexcept(NE) {
  if constexpr (std::is_base_of_v<internal_dispatch, D>) {
    static_assert(IsDirect);
    return D{}(std::in_place_type<P>,
               std::forward<add_qualifier_t<proxy<F>, Q>>(self),
               std::forward<Args>(args)...);
  } else if constexpr (Q == qualifier_type::rv) {
    proxy_helper::resetting_guard<P, F> guard{self};
    return invoke_dispatch_impl<D, R>(
        get_operand<IsDirect>(proxy_helper::get_ptr<P, F, Q>(std::move(self))),
        std::forward<Args>(args)...);
  } else {
    return invoke_dispatch_impl<D, R>(
        get_operand<IsDirect>(proxy_helper::get_ptr<P, F, Q>(
            std::forward<add_qualifier_t<proxy<F>, Q>>(self))),
        std::forward<Args>(args)...);
  }
}

内部调度是用来直接移动的,通常是我们要 relocate/upward conversion 才会到这个分支
剩下的就是调用,并根据左值/右值控制生命周期

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 P, class F, bool IsDirect, class D>
  static constexpr auto dispatcher =
      &invoke_dispatch<P, F, IsDirect, D, Q, NE, R, Args...>;

  template <class P, bool IsDirect, class D>
  static constexpr bool applicable_ptr =
      std::is_base_of_v<internal_dispatch, D> ||
      invocable_dispatch<P, IsDirect, D, Q, NE, R, Args...>;
  static constexpr qualifier_type qualifier = Q;
};

总的来说就是静态分发,并且保存一个静态的调用指针

meta & accessor

template <class F, bool IsDirect, class D, class O>
struct invocation_meta {
  constexpr invocation_meta() = default;
  template <class P>
  constexpr explicit invocation_meta(std::in_place_type_t<P>) noexcept
      : dispatcher(overload_traits<O>::template dispatcher<P, F, IsDirect, D>) {
  }

  typename overload_traits<O>::template dispatcher_type<F> dispatcher;
};

template <class... Ms>
struct composite_meta : Ms... {
  constexpr composite_meta() noexcept = default;
  template <class P>
  constexpr explicit composite_meta(std::in_place_type_t<P>) noexcept
      : Ms(std::in_place_type<P>)... {}
};

没什么好讲的,直接读就行
in_place_type 这一套太丑了,还不如老实写模板参数

template <class C, class... Os>
struct basic_conv_traits_impl : inapplicable_traits {};
template <class C, extended_overload... Os>
  requires(sizeof...(Os) > 0u)
struct basic_conv_traits_impl<C, Os...> : applicable_traits {};
template <class C>
struct basic_conv_traits : inapplicable_traits {};
template <class C>
  requires(requires {
    { typename C::dispatch_type() } noexcept;
    typename C::overload_types;
  } && is_is_direct_well_formed<C>() &&
           is_tuple_like_well_formed<typename C::overload_types>())
struct basic_conv_traits<C>
    : instantiated_t<basic_conv_traits_impl, typename C::overload_types, C> {};

如果有 dispatch_type 和 overload_types,合并所有的 overload,然后判断是否applicable
p.s. 这么多trait给我干哪来了,这不是 Rust 吗?

template <class T>
struct a11y_traits_impl
    : std::conditional<std::is_nothrow_default_constructible_v<T> &&
                           std::is_trivially_copyable_v<T> &&
                           !std::is_final_v<T>,
                       T, void> {};
template <class SFINAE, class T, class... Args>
struct a11y_traits : std::type_identity<void> {};
template <class T, class... Args>
struct a11y_traits<std::void_t<typename T::template accessor<Args...>>, T,
                   Args...>
    : a11y_traits_impl<typename T::template accessor<Args...>> {};
template <class T, class... Args>
using accessor_t = typename a11y_traits<void, T, Args...>::type;

这个 SFINAE 没绷住
a11y 应该是 accessibility,计算 accessor 的

template <class C, class F, class... Os>
struct conv_traits_impl : inapplicable_traits {};
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<invocation_meta<F, C::is_direct, typename C::dispatch_type,
                                     substituted_overload_t<Os, F>>...>;
  template <class T>
  using accessor =
      accessor_t<typename C::dispatch_type, T, typename C::dispatch_type,
                 substituted_overload_t<Os, F>...>;

  template <class P>
  static constexpr bool applicable_ptr =
      (overload_traits<substituted_overload_t<Os, F>>::template applicable_ptr<
           P, C::is_direct, typename C::dispatch_type> &&
       ...);
};

重点之二, meta 和 accessor 的定义

template <class C, class F>
struct conv_traits
    : instantiated_t<conv_traits_impl, typename C::overload_types, C, F> {};

最后封装

posted @ 2025-09-19 17:03  123789456ye  阅读(12)  评论(0)    收藏  举报