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
85
非类型参数会梦见 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
23
动态库,是得多动态?
lichray
0
35
Dynamically Loaded Libraries Outside the Standard
lichray
0
280
Thinking in Immediate: ImGUI
lichray
0
170
Lambda 和它们的环境
lichray
1
140
The Many Shades of reference_wrapper
lichray
0
170
Are We Macro-free Yet?
lichray
0
160
vvpkg: A cross-platform data deduplication library in C++14
lichray
0
97
Understanding Git
lichray
0
42
Other Decks in Programming
See All in Programming
fs2-io を試してたらバグを見つけて直した話
chencmd
0
230
CSC305 Lecture 26
javiergs
PRO
0
140
103 Early Hints
sugi_0000
1
230
create_tableをしただけなのに〜囚われのuuid編〜
daisukeshinoku
0
250
range over funcの使い道と非同期N+1リゾルバーの夢 / about a range over func
mackee
0
110
rails statsで大解剖 🔍 “B/43流” のRailsの育て方を歴史とともに振り返ります
shoheimitani
2
930
Go の GC の不得意な部分を克服したい
taiyow
3
780
PSR-15 はあなたのための ものではない? - phpcon2024
myamagishi
0
110
クリエイティブコーディングとRuby学習 / Creative Coding and Learning Ruby
chobishiba
0
3.9k
今年一番支援させていただいたのは認証系サービスでした
satoshi256kbyte
1
250
テストコード文化を0から作り、変化し続けた組織
kazatohiei
2
1.5k
42 best practices for Symfony, a decade later
tucksaun
1
180
Featured
See All Featured
Git: the NoSQL Database
bkeepers
PRO
427
64k
Raft: Consensus for Rubyists
vanstee
137
6.7k
Building a Scalable Design System with Sketch
lauravandoore
460
33k
Being A Developer After 40
akosma
87
590k
Visualization
eitanlees
146
15k
BBQ
matthewcrist
85
9.4k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
4 Signs Your Business is Dying
shpigford
181
21k
Testing 201, or: Great Expectations
jmmastey
40
7.1k
The Language of Interfaces
destraynor
154
24k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.9k
The Cult of Friendly URLs
andyhume
78
6.1k
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