跳到主要內容

std::optional 的未來?

std::optional<T&> 的未來?

draw

前言

春節就是拋拋走,趁著搭車的時間看完 JeanHeyd Meneide (ThePhantomDerpstorm/ThePhD) 的血淚控訴 To Bind and Loose a Reference1,他聲稱:標準委員會因為一個不存在的對手,訂定了一個殘缺的 std::optional。文章非常值得一讀,寫不出配得上的心得,只好寫一篇筆記。

Nullable 型別

C++17 增添了 std::optional<T> ,C++20 加入了 std::variant<Ts...> ,C++2x 預計會出現 std::expected<T> ,這些工具的相同點在於他們都是 nullable 型別 ─ 物件狀態可以是含有 T 或者不含有 T(也就是 null),雖然應用在不同的情境上,但某種程度上都可以理解為特化版的指標。

Reference 有甚麼問題?

std::optionalcppreference.com 裡的描述2 裡面有一句話:

There are no optional references; a program is ill-formed if it instantiates an optional with a reference type.

同樣適用於 std::variantstd::expected。其實在 Boost.Optional 或是 Boost.Variant 都是支援 reference 型別的,然而 C++ 標準委員會對下面這段程式分裂成兩派不同的見解 (範例取自 JeanHeyd Meneide):

#include <optional>
#include <iostream>

int main (int, char*[]) {
	int x = 5;
	int y = 24;
	std::optional<int&> opt = x;

	opt = y;
	std::cout << "x is " << x << std::endl;
	std::cout << "y is " << y << std::endl;

	return 0;
}

一派認為應該印出 524,也就是 opt = y 這個動作會將 reference 重新指向 y (rebinds);而另一派則認為應該印出 2424 ,也就是該動作將 x 賦值為 y 的值 (assigns through) 。

JeanHeyd Meneide 是屬於 rebinds 派,他的文章內用了個例子說明 assign through 的問題,節錄於後

std::optional<int&> cache::retrieve (std::uint64_t key) {
	std::optional<int&> opt = this->get_default_resource_maybe();
	// blah blah work
	auto found_a_thing = this->lookup_resource(key);
	if (found_a_thing) {
		int& resource = 
		  this->get_specific_resource(found_a_thing);
		// do stuff with resource, return
		opt = resource;
	}
	return opt; // optional says if we got something!
}

found_a_thingfalse 的時候,retrieve 回傳的值是一個 reference 到 default resource 的 optional,然而當 found_a_thingtrue 的時候,在 assign through 的作用下 opt = resource 會將 default resource 覆寫成 get_specific_resource 回傳的值 (完整的說法是回傳的 reference 所指涉的值);也就是說每次呼叫 retrieve ,只要有找到東西,default resource 的值就會改變。

這種 bug 可以算是夢靨級了吧?

不存在的對手

在標準委員會的會議中, JeanHeyd Meneide 主張的 rebinds 版本沒被接受 (過程曲折,讓他灰心到 quit ,請讀他的文章),之後他做了一些調查發現:現存的、有足夠使用者數量的、業界採用的 optional 實作版本,幾乎都沒有採用 assign through ,也就是委員會期望的 ─ 正確的 assign through 實作 ─ 根本是 rebinds 不存在的對手。

參考文件


  1. To Bind and Loose a Reference ↩︎

  2. cppreference ↩︎

留言

這個網誌中的熱門文章

得利油漆色卡編碼方式

得利油漆色卡編碼方式 類似 Munsell 色彩系統 ,編碼方式為 HUE LRV/CHROMA 例如 10GY 61/449 ( 色卡 ) 編碼數值 描述 10GY hue ,色輪上從 Y(ellow) 到 G(reen) 區分為 0 ~ 99 ,數值越小越靠近 Y,越大越靠近 G 61 LRV (Light Reflectance Value) 塗料反射光源的比率,數值從 0% ~ 100% ,越高越亮,反之越暗,也可理解為明度 449 chroma 可理解為彩度,數值沒有上限,越高顏色純度 (濃度) 越高 取決於測量儀器,對應至 RGB 並不保證視覺感受相同。 參考資料: 色卡對照網站 e-paint.co.uk Written with StackEdit .

UTF8 與 Unicode 的轉換 (C++)

UTF8 與 Unicode 的轉換 (C++) 先釐清一下這兩者的性質 Unicode: 為世界上所有的文字系統制訂的標準,基本上就是給每個字(letter)一個編號 UTF-8: 為 unicode 的編號制定一個數位編碼方法 UTF-8 是一個長度介於 1~6 byte 的編碼,將 unicode 編號 (code point) 分為六個區間如下表 1 Bits First code point Last code point Bytes Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 7 U+0000 U+007F 1 0xxxxxxx 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 觀察上面的表應該可以發現 除了 7 bits 的區間外,第一個 byte 開頭連續 1 的個數就是長度,例如 110XXXXX 就是 2 byte 長,而 1110xxxx 就是 3 byte 除了第一個 byte 外,之後的 byte 前兩個 bit 一定是 10 開頭,這樣的好處在於確立了編碼的 self-synchronizeing,意即當編碼為多個 byte 時,任取一個 byte 無法正常解碼。 Note 第一點中的例外 (7 bits) 是為了與 ASCII 的相容性,而第二點會影響到 code point 至 UTF-8 的轉換。 為了與 UTF-16 的相容性,在 R...

C++17 新功能 try_emplace

C++17 新功能 try_emplace 回顧 emplace 大家的好朋友 Standard Template Library (STL) 容器提供如 push_back , insert 等介面,讓我們塞東西進去; C++11 之後,新增了 emplace 系列的介面,如 std::vector::emplace_back , std::map::emplace 等,差異在於 emplace 是在容器內 in-place 直接建構新元素,而不像 push_back 在傳遞參數前建構,下面用實例來說明: struct Value { // ctor1 Value ( int size ) : array ( new char [ size ] ) , size ( size ) { printf ( "ctor1: %d\n" , size ) ; } // ctor2 Value ( const Value & v ) : array ( new char [ v . size ] ) , size ( v . size ) { printf ( "ctor2: %d\n" , size ) ; memcpy ( array . get ( ) , v . array . get ( ) , size ) ; } private : std :: unique_ptr < char [ ] > array ; int size = 0 ; } ; struct Value 定義了自訂建構子 (ctor1),以指定大小 size 配置陣列,複製建構子 (ctor2) 則會配置與來源相同大小及內容的陣列,為了方便觀察加了一些 printf 。當我們如下使用 std::vector::push_back 時 std :: vector < Value > v ; v . push_back ( Value ( 2048 ) ) ; 首先 Value 會先呼叫 ctor1,傳給 push_ba...