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
52
Dynamically Loaded Libraries Outside the Standard
lichray
0
410
Thinking in Immediate: ImGUI
lichray
0
200
Lambda 和它们的环境
lichray
1
170
The Many Shades of reference_wrapper
lichray
0
200
Are We Macro-free Yet?
lichray
0
160
vvpkg: A cross-platform data deduplication library in C++14
lichray
0
130
Understanding Git
lichray
0
44
Other Decks in Programming
See All in Programming
構造化・自動化・ガードレール - Vibe Coding実践記 -
tonegawa07
0
130
知って得する@cloudflare_vite-pluginのあれこれ
chimame
1
110
なぜあなたのオブザーバビリティ導入は頓挫するのか
ryota_hnk
0
250
レベル1の開発生産性向上に取り組む − 日々の作業の効率化・自動化を通じた改善活動
kesoji
1
340
副作用と戦う PHP リファクタリング ─ ドメインイベントでビジネスロジックを解きほぐす
kajitack
2
390
The Evolution of Enterprise Java with Jakarta EE 11 and Beyond
ivargrimstad
0
400
TypeScriptでDXを上げろ! Hono編
yusukebe
3
840
効率的な開発手段として VRTを活用する
ishkawa
1
180
「App Intent」よくわからんけどすごい!
rinngo0302
1
120
オンコール⼊⾨〜ページャーが鳴る前に、あなたが備えられること〜 / Before The Pager Rings
yktakaha4
2
1.1k
AI時代のソフトウェア開発を考える(2025/07版) / Agentic Software Engineering Findy 2025-07 Edition
twada
PRO
102
39k
20250708_JAWS_opscdk
takuyay0ne
2
140
Featured
See All Featured
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
A designer walks into a library…
pauljervisheath
207
24k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Statistics for Hackers
jakevdp
799
220k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
8
360
Building Applications with DynamoDB
mza
95
6.5k
The World Runs on Bad Software
bkeepers
PRO
70
11k
The Straight Up "How To Draw Better" Workshop
denniskardys
235
140k
Making Projects Easy
brettharned
116
6.3k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
710
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
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