C++ は関数テンプレートの候補を形成する際にテンプレート引数を代入します。 Substitution failure is not an error (SFINAE) は、その代入の immediate context にある無効な依存型または式がその候補を除外することを意味します。任意の無効な テンプレートコードを安全にするわけではありません。
候補の形成と immediate context #
オーバーロード解決は候補の形成から始まります。関数テンプレートでは、推論された引数 または明示的に指定された引数を、関数型とテンプレートパラメーター宣言に代入する必要が ある場合があります。その直接的な宣言コンテキストが無効なら、特殊化は破棄され、別の 実行可能なオーバーロードが選択されます。
#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 は適用されません。
template <class T>
auto broken(const T& value) {
return value.missing_member(); // hard error if this specialization is instantiated
}検出は実装本体ではなく、代入される宣言コンテキストに置いてください。
確立された検出テクニック #
std::enable_if_t は条件付きで型を提供するため、テンプレートパラメーター、パラメーター
型、戻り値型で役立ちます。
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 になるため、検出イディオムをサポートします。
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 と評価されますが、選択された
実装コード内のエラーは引き続きエラーです。
#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 で閲覧・コメントする(投稿にはログインが必要です)