跳到主要內容

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 ↩︎

留言