呼叫 std::max/std::min 之前你需要知道的大小事
很多人在初次接觸 STL (Standard Template Library) 的時候, 不免被它豐富的功能給吸引, 有相見恨晚的感覺; 但是在不夠了解它的情況下使用往往會造成預期外的結果. 以下簡單歸納 2 個 std::max()
/std::min()
適用的條件 (沒錯, 只有 2 個), 最後示範如何寫出比較好的 max()
/ min()
函式.
std::max()
只適合用來接受 lvalue, 而且型別必須相同
不管是從 cppreference 或是目前最新的 draft 來看, std::max()
的宣告如下:
這裡要特別注意的是回傳值型別: const T&
, 代表它會回傳 a 或 b 其中一個參考, 所以語意上和 ternary operator ?:
是相同的 (只是我們得用 reference to const 來接結果):
2. std::max()
可以用來接受 rvalue, 但要注意 lifetime
如果我們只想要物件的値, 根本不在乎兩者裡較大的是誰, 可以將回傳值用 copy assignment/constructor 來複製一份; 否則在呼叫 std::max()
的statement 結束以後, 須避免再次存取回傳的參考:
綜合以上的問題, 或許最好的解決方式就是不要用 std::max()
/std::min()
;或者我們可以實現更安全的版本, 在編譯的時候直接避免可能的錯誤. 我們要解決的核心問題是: 如何正確地決定回傳値型別?
首先可以創建函式模版, 並且使用 forwarding reference 來推導&保留引數的完整型別, 然後在回傳值的地方, 另外使用選擇器來決定型別, 值得一提的是, 這個模版可以接受不同型別的引數:
max_ret<T, U>
選擇器決定回傳型別的標準如下:
- 如果
T
,U
相同且皆為 lvalue reference to non-const: 回傳T
- 如果
T
,U
相同但是 (1) 以外的情形: 回傳remove_reference_const_t<T>
- 其他情形: 回傳
std::common_type_t<remove_reference_const_t<T>, remove_reference_const_t<U>>
當接收到的引數都是 lvalue reference to non-const 時, 我們可以確保回傳原值不會造成 dangling reference (這是 caller 給的保證); 其他情況下拿到的可能是暫時物件, 或者因為型別不同需要做 promotion, 這時候就需要回傳複製品, 而為了存取方便得消除這個複製品的 constness (如果有的話). 完整的 max()
的實作如下:
這份實作還有稍嫌不足的地方: 即無法支援自定型別. 如果要擴充應該怎麼做呢? 後續就留給讀者思考了