啟用 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>::typeR 的別名,而 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>::valuefalse。由於 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_MAXint。這是兩種可能的選擇:

// 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。