呼叫 std::max/std::min 之前你需要知道的大小事

Po Yen Chen
3 min readOct 9, 2019

很多人在初次接觸 STL (Standard Template Library) 的時候, 不免被它豐富的功能給吸引, 有相見恨晚的感覺; 但是在不夠了解它的情況下使用往往會造成預期外的結果. 以下簡單歸納 2 個 std::max()/std::min() 適用的條件 (沒錯, 只有 2 個), 最後示範如何寫出比較好的 max()/ min() 函式.

  1. 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> 選擇器決定回傳型別的標準如下:

  1. 如果 T, U相同且皆為 lvalue reference to non-const: 回傳 T
  2. 如果 T, U 相同但是 (1) 以外的情形: 回傳 remove_reference_const_t<T>
  3. 其他情形: 回傳 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() 的實作如下:

這份實作還有稍嫌不足的地方: 即無法支援自定型別. 如果要擴充應該怎麼做呢? 後續就留給讀者思考了

--

--