Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
非类型参数会梦见 OOP 吗?
Search
Zhihao Yuan
January 27, 2023
Programming
0
110
非类型参数会梦见 OOP 吗?
非类型模板参数对多态和参量化类型的启示。
视频:
https://www.bilibili.com/video/BV1bK411i7wL
Zhihao Yuan
January 27, 2023
Tweet
Share
More Decks by Zhihao Yuan
See All by Zhihao Yuan
Callable Objects in Java, C#, Rust, and C++
lichray
0
32
动态库,是得多动态?
lichray
0
59
Dynamically Loaded Libraries Outside the Standard
lichray
0
420
Thinking in Immediate: ImGUI
lichray
0
200
Lambda 和它们的环境
lichray
1
170
The Many Shades of reference_wrapper
lichray
0
210
Are We Macro-free Yet?
lichray
0
160
vvpkg: A cross-platform data deduplication library in C++14
lichray
0
140
Understanding Git
lichray
0
44
Other Decks in Programming
See All in Programming
Namespace and Its Future
tagomoris
6
710
Putting The Genie in the Bottle - A Crash Course on running LLMs on Android
iurysza
0
140
Ruby×iOSアプリ開発 ~共に歩んだエコシステムの物語~
temoki
0
340
Ruby Parser progress report 2025
yui_knk
1
450
そのAPI、誰のため? Androidライブラリ設計における利用者目線の実践テクニック
mkeeda
2
1.8k
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
400
AIを活用し、今後に備えるための技術知識 / Basic Knowledge to Utilize AI
kishida
22
5.8k
さようなら Date。 ようこそTemporal! 3年間先行利用して得られた知見の共有
8beeeaaat
3
1.5k
AI時代のUIはどこへ行く?
yusukebe
18
9k
為你自己學 Python - 冷知識篇
eddie
1
350
請來的 AI Agent 同事們在寫程式時,怎麼用 pytest 去除各種幻想與盲點
keitheis
0
120
基礎から学ぶ大画面対応(Learning Large-Screen Support from the Ground Up)
tomoya0x00
0
3.2k
Featured
See All Featured
Faster Mobile Websites
deanohume
309
31k
BBQ
matthewcrist
89
9.8k
Rails Girls Zürich Keynote
gr2m
95
14k
Visualization
eitanlees
148
16k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.9k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1.1k
GraphQLとの向き合い方2022年版
quramy
49
14k
Practical Orchestrator
shlominoach
190
11k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Intergalactic Javascript Robots from Outer Space
tanoku
272
27k
Large-scale JavaScript Application Architecture
addyosmani
513
110k
Transcript
非类型参数会梦见 OOP 吗? Zhihao Yuan <
[email protected]
>, 22-Oct-22
• 非类型(模版)参数 • 给 auto 模版参数传函数名 • nontype_t<f> • 原理
& 多态 3
Part 1 背景知识 4
非类型参数 (pre-C++14) • template<class T, size_t N> struct array; •
template<class T, T v> struct integral_constant; 5 有具体类型的非类型参数 类型为另一类型参数 的非类型参数
auto 非类型参数 (C++14) • template<auto v> struct constant; • constant<3>
→ decltype(v) is int • constant<'a'> → decltype(v) is char 6 同时具有值依赖和 类型依赖 constant<97>
• template<class T, T v> struct integral_constant; • integral_constant<std::nullptr_t, nullptr>
合法吗? 7
#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
• template<auto v> struct constant; 9
Constrained auto 参数 (C++20) • template<std::integral auto v> struct constant;
10
模版参数与函数参数的互换性 (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);
哪些值可以用非类型参数传递? • 3 • 3.14 • T* and T& •
&Class::member • enum • 用户定义类型? 12 C++20
Structural type (C++20) struct point { double x, y; };
13 基类全为 public structural 类型 成员全为 public structural 类型或 structural 类型的数组
指定初始化 structural 类型的对象 • template<point> void bar(); • bar<{.x =
3.0, .y = 5.0}>() 14
模版参数与函数参数的互换性 (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);
T& 非类型参数的用途 • 具有静态存储类的对象的地址是常量 16
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 }
T& 非类型参数的用途 • auto& 模版参数用于修改具有静态存储类的对象 • 具有静态存储类的对象的地址是常量 • 相比 auto,
auto const& 可传递非 structural 类型的对象 • 对象类型可能不是 literal type 18
Part 2 函数名作为非类型参数的实参 19
高阶函数 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 使用
高阶函数实现为模版特化 typedef int pivot_rule(vec); template<pivot_rule select_pivot> void algorithm_loop(tableau tab); int
dantzig(vec A) { /* ... */ } algorithm_loop<dantzig>(tab); 21 使用
非类型参数传递函数对象的特点 1. 同一算法对同一类型的函数依然可以产生不同特化 22
函数指针模版参数的灵活性 extern int dantzig(vec A); // 在其它文件中定义 algorithm_loop<dantzig>(tab); 23
非类型参数传递函数对象的特点 1. 同一算法对同一类型的函数指针依然可以产生不同特化 2. 和 lambda 表达式不同,是否内联回调函数是可选的 24
如果不是函数指针呢? template<pivot_rule select_pivot> void algorithm_loop(tableau tab); struct vec { int
find_maximum() const; /* ... */ }; 25 algorithm_loop<&vec::find_maximum>(tab) 可以吗?
同时支持函数指针和成员函数指针 template<auto select_pivot> void algorithm_loop(tableau tab); 26
任意符合签名的函数对象 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
非类型参数传递无捕捉的闭包 template<invocable_r<int, vec> auto select_pivot> void algorithm_loop(tableau tab); algorithm_loop<[](vec) {
return 0; }>(tab); 28
非类型参数传递函数对象的特点 1. 同一算法对同一类型的函数指针依然可以产生不同特化 2. 和 lambda 表达式不同,是否内联回调函数是可选的 3. auto 可接受不同类型的
callable 对象,包括模板实参列表里的 lambda 29
Part 3 如果没有 operator() 30
First-class 函数对象做参数 • 类型依赖+值依赖 template<auto fn> void algo(); • 类型依赖
void algo(auto fn); • 类型擦除 void algo(std::function<R(Args...)> fn); 31
旧式代码 struct IDoWorkCallback { virtual void OnEvent(WorkResult status, IData& object)
= 0; }; using IDoWorkCallbackPtr = std::shared_ptr<IDoWorkCallback>; struct WorkContext { void Add(IDoWorkCallbackPtr callback); }; 32
现代 C++ struct WorkContext { typedef void OnEvent(WorkResult status, IData&
object); void Add(std::function<OnEvent> callback); }; 33
现有代码 struct CMyWorkCallback : IDoWorkCallback { void OnEvent(WorkResult status, IData&
object) override { /* ... */ } }; ctx.Add(std::make_shared<CMyWorkCallback>()); 34 不是 std::function 也不是可调用的对象
一种迁移方法:增加重载 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
能避免引入额外对象吗? 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
用非类型参数传递信息 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
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
std::nontype template<auto> struct nontype_t { explicit nontype_t() = default; };
template<auto f> inline constexpr nontype_t<f> nontype; 39
使用新 API 的过渡代码 struct CMyReportingCallback : IDoWorkCallback { void OnEvent(WorkResult
status, IData& object) override; }; CMyReportingCallback cb; ctx.Add({ nontype<&CMyReportingCallback::OnEvent>, cb }); 40
无需覆写具体的成员函数 struct CMyReportingCallback : IDoWorkCallback { void OnEvent(WorkResult status, IData&
object) override; }; CMyReportingCallback cb; ctx.Add({ nontype<&CMyReportingCallback::OnEvent>, cb }); 41 Notify Notify
两个需求,一个解决方案 • 现有代码接受基于接口的回调 • 没有 operator(), 切换到基于类型擦 除的调用包装器需要某种适配 • 现有代码接受基于类型擦除的调用包
装器 • 调用签名匹配,但并非 operator() 的 成员函数需要某种适配 42 让用户指定某样东西作为他们对象的 operator()
CMyReportingCallback cb; ctx.Add({nontype<&CMyReportingCallback::Notify>, cb}); 43
推广到其它调用包装器 CMyReportingCallback cb; ctx.Add({std::nontype<&CMyReportingCallback::Notify>, cb}); 44 •Add(std::function) •Add(std::move_only_function) •Add(std::function_ref) •Add(std::copyable_function)
C++26?
用无捕捉闭包重写逻辑 CMyReportingCallback cb; ctx.Add({nontype< [](auto& cb, WorkResult status, IData& object)
{ LOG(INFO) << "status: " << status; cb.Notify(status, object); }>, cb}); 45
Part 4 从非类型参数的视角看多态性 46
nontype 改变了什么? • 原本没有 operator() 的类型,现在依然没有 • 原本不满足 invocable<Args...> concept
的类型,现在满足了 47
概念适配 struct CMyReportingCallback { void Notify(WorkResult status, IData& object); };
template<invocable<WorkResult, IData&> T> void Accept(T f); Accept(cb); 48
C++0x Concept struct CMyReportingCallback { void Notify(WorkResult status, IData& object);
}; concept_map invocable<CMyReportingCallback, WorkResult, IData&> { using operator() = CMyReportingCallback::Notify; }; 49
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
从类型检查到类型擦除 fn Accept<T>(cb: &T) where T: Callable<(WorkResult, IData)> fn Accept(cb:
&dyn Callable<(WorkResult, IData)>) 51
Trait Object 如何使用两个虚表 • impl 可以扩展已完成的类 • impl 块可以拆分到不同的模块中 •
C++ 继承自多个纯虚基类必须在定义类的同时完成 52 CMyReportingCallback IDoWorkCallback Callable<(WorkResult, IData)>
经典 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; };
回忆 C++ 对象模型 54 vtable CDraw for IDraw void draw();
void set_color(Color); ~IDraw() vptr … Color color_;
手工构造虚表实现类型擦除 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; };
广义的虚表 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
Swift Protocol extension CDraw : Drawable { func draw() {}
func set_color(_: Color) {} func close() {} } 57 protocol Drawable { func draw() func set_color(_: Color) func close() }
见证表 (Witness table) • 接口 conformance 的见证 • 可能描述了常量,关联类型等等 •
「类型」被表述为见证表的值 58 impl of CDraw for Drawable func draw(_: Self) func set_color(_: Self, _: Color); func close(_: Self)
抽象出见证表数据结构的作用 func Run<T: Drawable>(fg: T, bg: T) 59 两个实参的实际类型必须相同 同时也是类型擦除
当类型被建模为一个值 • 静态分发 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)
泛型函数传参 61 impl of CDraw for Drawable func draw(_: Self)
func set_color(_: Self, _: Color); func close(_: Self) // Small Buffer CDraw storage_; Drawable const&
结语 • OOP、类型擦除、参量化多态,在这两个方面有所不同: • 使用何种 Existential container • 如何引用 Witness
table 62
各种多态方案的实现 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 胖指针之一
结语 • OOP、类型擦除和参量化多态在 existential container 和传递 witness table 上有所不同 •
非类型参数可以在编译时携带多样的信息 • 在非类型参数或函数参数之间切换传递 witness table 的思想可以借鉴 64
Questions? 65 @lichray zhihaoy/nontype_functional