今天才知道這個Bug
1 |
|
gcc和clang的結果都是
1 | X = 1 and VA_ARGS = 2, 3 |
不過MSVC的結果是
1 | X = 1 and VA_ARGS = 2, 3 |
把以前的bug當feature了
修正方法有兩個
一個是在編譯的時候加上/Zc:preprocessor
,不過CMake project預設就開了
另一個是加上另外一層Macro
1 |
今天才知道這個Bug
1 | #define F(x, ...) X = x and VA_ARGS = __VA_ARGS__ |
gcc和clang的結果都是
1 | X = 1 and VA_ARGS = 2, 3 |
不過MSVC的結果是
1 | X = 1 and VA_ARGS = 2, 3 |
把以前的bug當feature了
修正方法有兩個
一個是在編譯的時候加上/Zc:preprocessor
,不過CMake project預設就開了
另一個是加上另外一層Macro
1 | #define EXPAND(x) x |
std::execution的部分繞不開Sender/Receiver,經過多次失敗之後終於寫出一個能跑的,紀錄一下
由於Receiver的範例比Sender簡單,所以從Receiver開始,而Sender先用Just代替
1 | #include <stdexec/execution.hpp> |
不過光是這樣一點用都沒有
至少要有有一個Callback function
1 | struct Recv { |
這樣才能跟Sender做結合
1 | auto o1 = stdexec::connect(stdexec::just(1), Recv()); |
至於Callback參數的形式,需要從Sender那邊定義,之後會寫一個簡單的Sender
1 | struct Send { |
跟Rece
類似,這邊要有一個sender_concept
不過一樣沒什麼用,最小的實現至少是這樣子
1 | struct Send { |
使用方式跟上面差不多
1 | auto o2 = stdexec::connect(Send{}, Recv()); |
先不看op
的部分,在Send
有兩個部分
1 | using completion_signatures = stdexec::completion_signatures<stdexec::set_value_t(int)>; |
這個定義皆在後面的Receiver
該接受什麼類型的參數
對照Recv
1 | struct Recv { |
兩個需要成對,不然connect
的部分會出錯
在connect
的階段,演算法會呼叫
1 | friend op<R> tag_invoke(stdexec::connect_t, Send, R r) { |
tag_invoke的地方不細說,由於我們不知道真正的Receiver類型是什麼,所以需要一個template版本的op
這邊也只有將Sender
和Receiver
連接起來,還沒開始執行
執行的部分在
1 | stdexec::start(o2); |
演算法這時候就會呼叫
1 | template <typename R> |
將42送到Receiver
– [What are Senders Good For, Anyway?]What are Senders Good For, Anyway? – Eric Niebler
– 浅谈The C++ Executors
– c++ execution 与 coroutine (一) : CPO与tag_invoke
– c++ execution 与 coroutine (二) : execution概述
– c++ execution 与 coroutine (三):最简单的receiver与最简单的sender
趁出去玩之前發一下文章,在C++ Reflection還沒正式定案之前,總有自救會想盡辦法來解決這些問題
在C++20之前,最簡單的方法就是用Macro了
1 | #include <iostream> |
Macro的方案都差不多,不過問題是出在Macro,由於在Preprocessor階段,得不到C++2的語意,所以會遇上以下問題
接著就到了C++20時期了
在這之前要先介紹一個核心知識,沒有這個都辦不到
1 | // C++17 |
__PRETTY_FUNCTION__
是gcc/clang特有的,Visual C++有等價的__FUNCSIG__
,不過在C++20之後,用std::source_location::current().function_name()
就好,接下來的問題就變成了,如何將struct的information成為funcion name的一部分`
在C++20當中,NTTP能居受的種類更多了,由於這樣,很難推斷出類型,所以template <auto>
發揮出功用了
1 | template <auto V> |
輸出結果
1 | void test() [with auto V = &obj::field] |
到目前為止,這段程式碼還不是太有用,因為印出了太多不需要的東西,所以要對輸出Function Name做處理
用constexpr std::string_view做處理
1 | template <auto V> |
MSVC和gcc/clang的處理方式不同,要個別處理
出去玩了一趟,好久沒寫一些東西,不然都要乾涸了
這觀念也很簡單,假設我們有類似這樣的程式碼
1 | template <typename T> |
這段程式在C++20是編譯不過,可是C++23放鬆了限制,允許這種寫法
不過根據神人的解法,在C++20可以模擬這種動作
1 | template <class... T> |
雖然繞了一點,但是能用
Type Punning是指用不同類型的Pointer,指向同一塊Memory address的行為,這是Undefined beahvior,可能會造成未知的錯誤.
例如
1 | #include <iostream> |
Type punning違反了Strict aliasing rule
寫網路程式的時候常常會遇到這種情形,分配一塊記憶體,然後Cast成另外一種Type的Pointer填值
1 | typedef struct Msg |
C語言的話可以使用union
1 | union { |
或是使用(unisnged / signed) char *
取代上面的int*
可以認為j從char*
轉匯成type *
是合法的,反之不成立
1 | int x = 42; |
這樣是合法的,不過缺點就是要多一次拷貝
C++20引進的新東西,不過實作也就只是上面的memcpy包裝
1 | template <class To, class From> |
C++23引進的新觀念,類似於reinterpret_cast,不過沒有undefined behaviro的副作用
1 | struct ProtocolHeader { |
一開始看到GAT也不知道在幹嘛,是看到Could someone explain the GATs like I was 5?才有感覺]
最簡單的範例,現在有一個struct
1 | struct Foo { bar: Rc<String>, } |
假設你要同時支援 Rc
和’Arc
的版本
該怎麼做
1 | struct FooRc { bar: Rc<String>, } |
不過這當然沒什麼好說的
理論上辦得到,不過沒什麼優點
我希望能寫成這樣
1 | struct Foo<P: Pointer> { bar: P<String>, } |
這樣是編譯不會過的,有了GAT之後,可以寫成這樣
1 | trait PointerFamily { type Pointer<T>; } |
不過用C++對我來說反而更好理解,就是用nested template來做
首先是等價的版本
1 | template <typename T> |
不過對於這問題,還有更簡單的方法
用template template parameter即可
1 | template <typename T> |
1 | trait Mappable { |
等價的C++版本大概是
1 | template <class T> |
這裡的Resouce不光指Memory,可能是FILE,或是ffmpeg那種Handle
Resource Management一直都是個討論的重點,要混合在C++使用,有很多種方法
拿FILE來舉例好了
1 | FILE *fp = fopen(...); |
這種方法最直接,不用學其他額外的方法,不過常常會因為程式碼的改變,而忘記release resource這件事,因此才有其他流派生存的機會
大概的程式碼長這樣,不過在C++不一定叫defer,可能叫ScopeGuard之類的東西,不過原理是一樣的
1 | FILE *fp = fopen(...); |
在小規模的使用是沒問題的,當Resoruce 一多就會變得冗餘,例如
1 | FILE *fp1 = fopen(...); |
於是C++ RAII的方式出現了,有鑑於shared_ptr
耗費較多的資源,這邊的方案都是unique_ptr
為主
為每個resource寫出一個Wrapper
1 | struct FILEWrapper { |
沒什麼不好,只是工作量太大,每加一種Resource就要有個Wrapper,那有沒有其他方案
同樣以FILE舉例,新增一個function object
1 | #include <stdio.h> |
這樣看起來跟上面差不了多少
另一種方法是
1 | std::unique_ptr<FILE, int(*)(FILE *)> fp(fp, fclose); |
這種方式比上面那個還差
雖然跟上面無關,不過這也是unique_ptr的一部分,一併提出
由於API設計的關係,input需要的是double pointer
程式有些可能會變成這樣
1 | std::unique_ptr<ITEMIDLIST_ABSOLUTE, CoTaskMemFreeDeleter> pidl; ITEMIDLIST_ABSOLUTE* rawPidl; |
這時候就是out_ptr使用場警
1 | std::unique_ptr<ITEMIDLIST_ABSOLUTE, CoTaskMemFreeDeleter> pidl; |
雖然這是在C++23才進入標準庫,不過
GitHub - soasis/out_ptr: Repository for a C++11 implementation of std::out_ptr (p1132), as a standalone library!
已經可以先嘗鮮了
C++17之後,放寬template的要求
於是這樣的程式碼成為可能
1 | template <auto destroy> |
配合上C++20的Concept之後,成為威力強大的武器
以下是從Meeting CPP 2022中節錄出來的片段
1 | template <typename T, auto * ConstructFunction, auto * DestructFunction> |
幾乎修正了上面所說的痛點
使用上也只要
1 | c_resource<FILE, fopen, fclose> fp; |
算是目前看到最通用的解法
這算是另闢新徑的方案,RAII的方案都把release resource放在destructor中
自從C++20引進Corotuine,產生了新的可能
使用上大概會是這樣
1 | co_resource<FILE*> usage() { |
滿足C++11中對Alloocator的需求,所能寫出的最簡單allocator
注意
1 | #include <cstdlib> |
而要用自己的Allocate就可以這麼做
1 | std::vector<int, Minallocator<int>> v; |
不常用,有用到再說
已知T
類型的Allocator,想要根據相同策略拿到U
類型的Allocator
也就是說希望用同樣的方式來分配U
可以透過
1 | allocator<U>=allocator<T>::rebind<U>::other. |
拿到,因此
std::allcoator<T>::rebind<U>::other
等同於std::allcoator<U>
Myallcoator<T>::rebind<U>::other
等同於Myallcoator<U>
在libstdc++中的實現
1 | template <typename _Tp1> |
這樣的程式碼會有問題
1 | ector<int, Minallocator<int>> pool_vec { 1, 2, 3, 4 }; |
因為兩者的Allocator Type不同,所以直接複製不行,所以只要兩者相同就行了,也就是C++17 PMR的初衷
新提出來的memory_resource
是個asbtract class,不同的instance會有不同的行為
因此可以可以這樣做
1 | // define allocation behaviour via a custom "memory_resource" |
1 | #include <iostream> |
透過上面的index_addr
,name_addr
,fun_print_addr
等,可以對object進行操作
而反射主要分成兩部分
雖然目前的官方標準還沒出來,不過現在有兩大流派
什麼辦不到的事情,用Marco就好了
以Boost Describe舉例
1 | struct X |
其他Macro Based的方案也差不多,就是另外定義一個Macro,自動生成類似上面的Metadata
不過這邊的問題就是
另外一派就是借助libclang來動手生成,透過Parse C++ AST來生成需要的API
舉例說明
1 | class MyClass |
生成的Metadata可以這麼使用
1 | reflang::Class<MyClass> metadata; |
這個方案的問題在於
雖然現有的Reflection library多的跟山一樣,不過眾口難調,有些是針對特定用途設計的,無法涵蓋其他方面的使用,有些功能完整,但是難用
於是乎就有人想要對語法方面下手,成為C++ Standard中的一部分
1 | template <class T> |
reflexpr和decltype一樣是type-based,所以可以套用到type based metaprogramming中
不過會不會成為標準是另外一回事了
跟Network Library一樣,成為標準之前先用成熟的方案解決
How to write comparsion operator for custom type
假設我們有一個類別
1 | struct Value { |
我們要怎麼寫出的程式碼
1 | Value v1, v2; |
有幾種方式
一種是當member function存在
手動寫出所有comparsion operator
1 | struct Value { |
另外一種是Free function存在
1 | bool operator<(const Value &lhs, const Value &rhs) { return lhs.v < rhs.v; } |
兩種實現原理相同,看情況選擇要用哪種,現在要討論的是其他的問題
當我們需要支持更多運算符號時,我們就需要寫更多的Function
1 | bool operator>(const Value &lhs, const Value &rhs); |
如果我們需要支援另外一種Type
1 | struct Value1 { |
然後又要出現一堆複製貼上加上手動修改的產物
1 | bool operator<(const Value1 &lhs, const Value1 &rhs); |
寫起來麻煩又沒什麼技術含量
有些operator可以用其他operator表示,例如Not Equal就是Not + Equal
所以我們可以用CRTP技巧減少我們的程式碼
1 | template<class Derived> |
其他的operator可以如法炮製,很多的C++ Graphics/Math Library都用了這個技巧
只要實作<
和==
,可以用來推導出其他四種比較關係
不過很不直觀,CRTP就是一種Hack,那有沒有更好的方法
Spaceship oerator也叫做The Three-Way Comparison Operator
這是C++20的一個特性,直接上Code來說明
1 | #include <compare> |
而Compiler直接為你生成Comparsion Code,原先的程式碼視為這樣
1 | (a <=> b) < 0 //true if a < b |
這種方式類似於strcmp
,會回傳<0
,>0
,0
三種情形
基本上這樣就滿足了80%的需求了,不過人生最難的就是那個But
有需要的話自定義比較方式的話,可以自定義comparsion operator
1 | struct Value1 { |
不過現在spaceship operator必須回傳的是std::strong_ordering
,std::weak_ordering
,std::partial_ordering
其中之一
至於三種ordering的差異,在此不探討,需要的話去Reference看,大部分只需要std::strong_ordering
即能完成需求