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 上阅读或评论(发表评论需要登录)