啟用 if
std::enable_if
是一個使用布林條件觸發 SFINAE 的便捷工具。它被定義為:
template <bool Cond, typename Result=void>
struct enable_if { };
template <typename Result>
struct enable_if<true, Result> {
using type = Result;
};
也就是說,enable_if<true, R>::type
是 R
的別名,而 enable_if<false, T>::type
是不正確的,因為 enable_if
的特化沒有 type
成員型別。
std::enable_if
可用於約束模板:
int negate(int i) { return -i; }
template <class F>
auto negate(F f) { return -f(); }
在這裡,對 negate(1)
的呼叫會由於含糊不清而失敗。但是第二個過載並不打算用於整數型別,所以我們可以新增:
int negate(int i) { return -i; }
template <class F, class = typename std::enable_if<!std::is_arithmetic<F>::value>::type>
auto negate(F f) { return -f(); }
現在,例項化 negate<int>
會導致替換失敗,因為 !std::is_arithmetic<int>::value
是 false
。由於 SFINAE,這不是一個硬錯誤,這個候選者只是從過載集中刪除。因此,negate(1)
只有一個可行的候選人 - 然後被稱為。
何時使用它
值得記住的是,std::enable_if
是 SFINAE 之上的幫手,但這並不是 SFINAE 首先發揮作用的原因。讓我們考慮這兩個替代方案來實現類似於 std::size
的功能,即產生容器或陣列大小的過載集 size(arg)
:
// for containers
template<typename Cont>
auto size1(Cont const& cont) -> decltype( cont.size() );
// for arrays
template<typename Elt, std::size_t Size>
std::size_t size1(Elt const(&arr)[Size]);
// implementation omitted
template<typename Cont>
struct is_sizeable;
// for containers
template<typename Cont, std::enable_if_t<std::is_sizeable<Cont>::value, int> = 0>
auto size2(Cont const& cont);
// for arrays
template<typename Elt, std::size_t Size>
std::size_t size2(Elt const(&arr)[Size]);
假設 is_sizeable
被恰當地寫入,這兩個宣告應該與 SFINAE 完全相同。哪個是最容易編寫的,哪個最易於檢視和理解?
現在讓我們考慮一下如何實現算術助手,避免有符號整數溢位,有利於環繞或模組化行為。也就是說,例如 incr(i, 3)
將與 i += 3
相同,除非結果總是被定義,即使 i
是具有值 INT_MAX
的 int
。這是兩種可能的選擇:
// handle signed types
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(-1) < static_cast<Int>(0)]>;
// handle unsigned types by just doing target += amount
// since unsigned arithmetic already behaves as intended
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(0) < static_cast<Int>(-1)]>;
template<typename Int, std::enable_if_t<std::is_signed<Int>::value, int> = 0>
void incr2(Int& target, Int amount);
template<typename Int, std::enable_if_t<std::is_unsigned<Int>::value, int> = 0>
void incr2(Int& target, Int amount);
再次哪個是最容易編寫的,哪個是最容易檢視和理解的?
std::enable_if
的優勢在於它如何與重構和 API 設計相結合。如果 is_sizeable<Cont>::value
是為了反映 cont.size()
是否有效,那麼只使用 size1
所顯示的表示式可以更簡潔,儘管這可能取決於 is_sizeable
是否會在幾個地方使用。與 std::is_signed
形成鮮明對比,與其實施內容相比,std::is_signed
更明確地反映了其意圖 28。