$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
非类型参数会梦见 OOP 吗?
Search
Zhihao Yuan
January 27, 2023
Programming
0
120
非类型参数会梦见 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
34
动态库,是得多动态?
lichray
0
69
Dynamically Loaded Libraries Outside the Standard
lichray
0
430
Thinking in Immediate: ImGUI
lichray
0
220
Lambda 和它们的环境
lichray
1
180
The Many Shades of reference_wrapper
lichray
0
220
Are We Macro-free Yet?
lichray
0
160
vvpkg: A cross-platform data deduplication library in C++14
lichray
0
160
Understanding Git
lichray
0
47
Other Decks in Programming
See All in Programming
Graviton と Nitro と私
maroon1st
0
140
AI前提で考えるiOSアプリのモダナイズ設計
yuukiw00w
0
190
LLMで複雑な検索条件アセットから脱却する!! 生成的検索インタフェースの設計論
po3rin
4
980
ローカルLLMを⽤いてコード補完を⾏う VSCode拡張機能を作ってみた
nearme_tech
PRO
0
170
ELYZA_Findy AI Engineering Summit登壇資料_AIコーディング時代に「ちゃんと」やること_toB LLMプロダクト開発舞台裏_20251216
elyza
2
630
AIコーディングエージェント(NotebookLM)
kondai24
0
230
Navigation 3: 적응형 UI를 위한 앱 탐색
fornewid
1
460
re:Invent 2025 トレンドからみる製品開発への AI Agent 活用
yoskoh
0
430
AIエンジニアリングのご紹介 / Introduction to AI Engineering
rkaga
8
3.4k
ゲームの物理 剛体編
fadis
0
370
안드로이드 9년차 개발자, 프론트엔드 주니어로 커리어 리셋하기
maryang
1
140
Flutter On-device AI로 완성하는 오프라인 앱, 박제창 @DevFest INCHEON 2025
itsmedreamwalker
1
150
Featured
See All Featured
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
58
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
51
46k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
33
YesSQL, Process and Tooling at Scale
rocio
174
15k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
69
Making the Leap to Tech Lead
cromwellryan
135
9.7k
Optimizing for Happiness
mojombo
379
70k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
5k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
2
2.8k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.8k
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