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
这个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> {};
最后封装
There is a negligible beginning in all great action and thought.

浙公网安备 33010602011771号