Upgrade to Pro — share decks privately, control downloads, hide ads and more …

非类型参数会梦见 OOP 吗?

非类型参数会梦见 OOP 吗?

非类型模板参数对多态和参量化类型的启示。

视频: https://www.bilibili.com/video/BV1bK411i7wL

Zhihao Yuan

January 27, 2023
Tweet

More Decks by Zhihao Yuan

Other Decks in Programming

Transcript

  1. 非类型参数 (pre-C++14) • template<class T, size_t N> struct array; •

    template<class T, T v> struct integral_constant; 5 有具体类型的非类型参数 类型为另一类型参数 的非类型参数
  2. auto 非类型参数 (C++14) • template<auto v> struct constant; • constant<3>

    → decltype(v) is int • constant<'a'> → decltype(v) is char 6 同时具有值依赖和 类型依赖 constant<97>
  3. #include <concepts> • template<std::integral T, T v> struct integral_constant; •

    integral_constant<std::nullptr_t, nullptr> → error: constraints not satisfied for class template 'integral_constant' [with T = std::nullptr_t, v = nullptr] 8
  4. 模版参数与函数参数的互换性 (1/2) • foo<3>() • template<auto v> void foo(); •

    template<std::integral auto v> void foo(); 11 • foo(3) • void foo(auto v); • void foo(std::integral auto v);
  5. 哪些值可以用非类型参数传递? • 3 • 3.14 • T* and T& •

    &Class::member • enum • 用户定义类型? 12 C++20
  6. Structural type (C++20) struct point { double x, y; };

    13 基类全为 public structural 类型 成员全为 public structural 类型或 structural 类型的数组
  7. 模版参数与函数参数的互换性 (2/2) • foo<3>() • template<auto v> void foo(); •

    template<std::integral auto v> void foo(); • bar<{.x = 5.0}>() • template<point> void bar(); 15 • foo(3) • void foo(auto v); • void foo(std::integral auto v); • bar({.x = 5.0}) • void bar(point);
  8. auto& 可以修改模板实参 template<auto& r> void modify() { r = 3;

    } 17 int main() { static int x; printf("x = %d\n", x); // x = 0 modify<x>(); printf("x = %d\n", x); // x = 3 }
  9. 高阶函数 in C typedef int pivot_rule(vec); void algorithm_loop(tableau tab, pivot_rule

    select_pivot); int dantzig(vec A) { /* ... */ } algorithm_loop(tab, dantzig); 20 使用
  10. 如果不是函数指针呢? template<pivot_rule select_pivot> void algorithm_loop(tableau tab); struct vec { int

    find_maximum() const; /* ... */ }; 25 algorithm_loop<&vec::find_maximum>(tab) 可以吗?
  11. 任意符合签名的函数对象 template<class Fn, class R, class... T> concept invocable_r =

    std::is_invocable_r_v<R, Fn, T...>; template<invocable_r<int, vec> auto select_pivot> void algorithm_loop(tableau tab); 27
  12. First-class 函数对象做参数 • 类型依赖+值依赖 template<auto fn> void algo(); • 类型依赖

    void algo(auto fn); • 类型擦除 void algo(std::function<R(Args...)> fn); 31
  13. 旧式代码 struct IDoWorkCallback { virtual void OnEvent(WorkResult status, IData& object)

    = 0; }; using IDoWorkCallbackPtr = std::shared_ptr<IDoWorkCallback>; struct WorkContext { void Add(IDoWorkCallbackPtr callback); }; 32
  14. 现代 C++ struct WorkContext { typedef void OnEvent(WorkResult status, IData&

    object); void Add(std::function<OnEvent> callback); }; 33
  15. 现有代码 struct CMyWorkCallback : IDoWorkCallback { void OnEvent(WorkResult status, IData&

    object) override { /* ... */ } }; ctx.Add(std::make_shared<CMyWorkCallback>()); 34 不是 std::function 也不是可调用的对象
  16. 一种迁移方法:增加重载 struct WorkContext { typedef void OnEvent(WorkResult status, IData& object);

    void Add(std::function<OnEvent> callback); void Add(IDoWorkCallbackPtr callback) { Add([=](WorkResult status, IData& object) { callback->OnEvent(status, object); }); } }; 35
  17. 能避免引入额外对象吗? struct WorkContext { typedef void OnEvent(WorkResult status, IData& object);

    void Add(std::function<OnEvent> callback); void Add(IDoWorkCallbackPtr callback) { Add(std::bind_front(&IDoWorkCallback::OnEvent, callback)); } }; 36 C++20
  18. 用非类型参数传递信息 struct WorkContext { typedef void OnEvent(WorkResult status, IData& object);

    void Add(std::function<OnEvent> callback); void Add(IDoWorkCallbackPtr callback) { Add({ nontype<&IDoWorkCallback::OnEvent>, callback }); } }; 37
  19. P2511 struct WorkContext { typedef void OnEvent(WorkResult status, IData& object);

    void Add(std::function<OnEvent> callback); void Add(IDoWorkCallbackPtr callback) { Add({ nontype<&IDoWorkCallback::OnEvent>, callback }); } }; 38
  20. std::nontype template<auto> struct nontype_t { explicit nontype_t() = default; };

    template<auto f> inline constexpr nontype_t<f> nontype; 39
  21. 使用新 API 的过渡代码 struct CMyReportingCallback : IDoWorkCallback { void OnEvent(WorkResult

    status, IData& object) override; }; CMyReportingCallback cb; ctx.Add({ nontype<&CMyReportingCallback::OnEvent>, cb }); 40
  22. 无需覆写具体的成员函数 struct CMyReportingCallback : IDoWorkCallback { void OnEvent(WorkResult status, IData&

    object) override; }; CMyReportingCallback cb; ctx.Add({ nontype<&CMyReportingCallback::OnEvent>, cb }); 41 Notify Notify
  23. 两个需求,一个解决方案 • 现有代码接受基于接口的回调 • 没有 operator(), 切换到基于类型擦 除的调用包装器需要某种适配 • 现有代码接受基于类型擦除的调用包

    装器 • 调用签名匹配,但并非 operator() 的 成员函数需要某种适配 42 让用户指定某样东西作为他们对象的 operator()
  24. 概念适配 struct CMyReportingCallback { void Notify(WorkResult status, IData& object); };

    template<invocable<WorkResult, IData&> T> void Accept(T f); Accept(cb); 48
  25. C++0x Concept struct CMyReportingCallback { void Notify(WorkResult status, IData& object);

    }; concept_map invocable<CMyReportingCallback, WorkResult, IData&> { using operator() = CMyReportingCallback::Notify; }; 49
  26. Rust Traits trait Callable<Args> { fn call(&self, args: Args); }

    impl Callable<(WorkResult, IData)> for CMyReportingCallback { fn call(&self, (status, object): (WorkResult, IData)) { self.Notify(status, object); } } 50
  27. Trait Object 如何使用两个虚表 • impl 可以扩展已完成的类 • impl 块可以拆分到不同的模块中 •

    C++ 继承自多个纯虚基类必须在定义类的同时完成 52 CMyReportingCallback IDoWorkCallback Callable<(WorkResult, IData)>
  28. 经典 OOP in C++ class CDraw : public IDraw {

    Color color_; public: void draw() override; void set_color(Color) override; ~CDraw() = default; }; 53 class IDraw { public: virtual void draw() = 0; virtual void set_color(Color) = 0; ~IDraw() = default; };
  29. 回忆 C++ 对象模型 54 vtable CDraw for IDraw void draw();

    void set_color(Color); ~IDraw() vptr … Color color_;
  30. 手工构造虚表实现类型擦除 constinit Drawable DrawableImpl = { .draw = [](void* this_)

    {}, .set_color = [](void* this_, Color color) {}, .destroy = [](void* this_) {}, }; 55 struct Drawable { typedef void draw_t(void*); typedef void set_color_t(void*, Color); typedef void destroy_t(void*); draw_t* draw; set_color_t* set_color; destroy_t* destroy; };
  31. 广义的虚表 56 DrawableImpl 静态对象 void (*draw)(void*); void (*set_color)(void*, Color); void

    (*destroy)(void*); ref-wrap<Drawable const> CDraw rep_; ref-wrap<Drawable const> void* obj_; 引用语义 type erasure 或 P0957 “proxy” 值语义 type erasure
  32. Swift Protocol extension CDraw : Drawable { func draw() {}

    func set_color(_: Color) {} func close() {} } 57 protocol Drawable { func draw() func set_color(_: Color) func close() }
  33. 见证表 (Witness table) • 接口 conformance 的见证 • 可能描述了常量,关联类型等等 •

    「类型」被表述为见证表的值 58 impl of CDraw for Drawable func draw(_: Self) func set_color(_: Self, _: Color); func close(_: Self)
  34. 当类型被建模为一个值 • 静态分发 template<auto const& witness> void Run(e<CDraw> fg, e<CDraw>

    bg); • e 代表 existential container • 动态分发 void Run(auto const& witness, e<CDraw> fg, e<CDraw> bg); 60 func Run<T: Drawable>(fg: T, bg: T)
  35. 泛型函数传参 61 impl of CDraw for Drawable func draw(_: Self)

    func set_color(_: Self, _: Color); func close(_: Self) // Small Buffer CDraw storage_; Drawable const&
  36. 各种多态方案的实现 63 Existential container Witness table C++ 多态对象 指针 内嵌

    vptr Rust trait objects 胖指针,基于不同引用 胖指针之一 Swift generics SBO 通过静态或运行时的参数传 递,保存时使用更大的容器 std::function_ref 胖指针 无,直接指向 thunk std::move_only_function SBO vptr 存在容器内,可修改 pro::proxy from P0957 胖指针,可选 owning 胖指针之一
  37. 结语 • OOP、类型擦除和参量化多态在 existential container 和传递 witness table 上有所不同 •

    非类型参数可以在编译时携带多样的信息 • 在非类型参数或函数参数之间切换传递 witness table 的思想可以借鉴 64