SFINAE 與樣板多載解析

最後更新: 2026-01-21 08:29:00 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

C++ 在形成函式樣板候選項目時會替換樣板引數。替換失敗不是錯誤(SFINAE)表示相依型別或運算式在替換的立即內容中無效時,會移除該候選項目;它不會讓任意無效的樣板程式碼變得安全。

候選項目形成與立即內容 #

多載解析會先形成候選項目。對函式樣板而言,推導或明確提供的引數可能需要替換至其函式型別與樣板參數宣告中。如果該立即宣告內容無效,特化會被捨棄,並可能選取另一個可行的多載。

cpp
#include <cstddef>
#include <concepts>
#include <type_traits>

template <class T>
auto element_count(const T& value) -> decltype(value.size()) {
    return value.size();
}

template <class T, std::size_t N>
constexpr std::size_t element_count(const T (&)[N]) {
    return N;
}

對陣列而言,第一個候選項目會失敗,因為陣列沒有 size() 成員。該失敗發生在尾端回傳型別中,因此陣列多載仍可使用。當非樣板多載是更佳匹配時,也可能勝出。

SFINAE 不會在候選項目已被選取後,僅因其函式本體無法編譯就適用:

cpp
template <class T>
auto broken(const T& value) {
    return value.missing_member(); // hard error if this specialization is instantiated
}

請將偵測放在會進行替換的宣告內容中,而不是實作本體中。

既有的偵測技術 #

std::enable_if_t 有條件地提供型別,因此適合用於樣板參數、參數型別或回傳型別。

cpp
template <class T,
          std::enable_if_t<std::is_integral_v<T>, int> = 0>
T twice(T value) {
    return value * 2;
}

decltype 會測試運算式是否格式正確。std::void_t 支援偵測慣用法,因為提供的每個型別都有效時,它會變成 void。

cpp
template <class, class = void>
struct has_value_type : std::false_type {};

template <class T>
struct has_value_type<T, std::void_t<typename T::value_type>>
    : std::true_type {};

當運算式需要值類別但無法或不應建立物件時,請在 decltype 等未求值運算元中使用 std::declval<T>()。

新介面優先使用限制 #

C++20 限制會直接表達可用性,並參與多載解析。它保留相同區分:requires 運算式中的無效運算式,在其需求內容中會評估為 false;而選定實作程式碼中的錯誤仍然是錯誤。

cpp
#include <concepts>
#include <cstddef>

template <class T>
concept Sized = requires (const T& value) {
    { value.size() } -> std::convertible_to<std::size_t>;
};

template <Sized T>
auto element_count(const T& value) {
    return value.size();
}

限制會產生表達意圖的宣告,並提供更好的診斷。對必須支援 C++20 之前語言模式,或其多載集合已依賴替換失敗的介面,請保留使用 SFINAE。

評論

在 GitHub 上閱讀或評論(發表評論需要登入)