引言
最近在实现vector的时候,遇到了一个问题:
|
|
对于上面一个模板函数,如果按照上面那种写法,当调用v.insert(pos,1,2);的时候,可能也会走进上面那个函数并将InputIt推导为int类型,而实际情况是想要往pos位置上插入1个2。这就会导致编译器不知道调用哪个函数。那么应当如何解决这一个问题呢?下面就要介绍这篇文章的主人公了。
SFINAE
SFINAE(Substitution Failure Is Not An Error,替换失败不是错误)是 C++ 模板元编程中的一个重要规则。它的核心思想是:在模板实例化过程中,如果某个模板参数替换失败(例如,类型不匹配或表达式不合法),编译器不会报错,而是会忽略这个模板特化,继续尝试其他可能的模板特化或重载。
SFINAE 的核心思想
- 替换(Substitution):
- 在模板实例化时,编译器会用实际的类型或值替换模板参数。
- 例如,对于
template<typename T> void foo(T t),如果调用foo(42),编译器会用int替换T。
- 替换失败(Substitution Failure):
- 如果在替换过程中,某个表达式或类型不合法(例如,类型没有某个成员函数,或操作符不支持),替换就会失败。
- 不是错误(Not An Error):
- 如果替换失败,编译器不会报错,而是会忽略这个模板特化,继续尝试其他可能的模板特化或重载。
SFINAE常用工具
std::enable_if
|
|
- 如果条件
B为true,std::enable_if<B, T>会定义嵌套类型type,其值为T。 - 如果条件
B为false,std::enable_if<B, T>不会定义嵌套类型type,从而导致替换失败(SFINAE)。
例如:
|
|
std::void_t
std::void_t 的核心思想是利用模板的替换规则:
- 如果传递给
std::void_t的类型或表达式是合法的,那么std::void_t会生成void类型。 - 如果传递给
std::void_t的类型或表达式是非法的(例如,某个类型不存在或某个操作不支持),那么模板替换会失败,触发 SFINAE,编译器会忽略这个模板特化,而不会报错。
std::declval
std::declval 的定义如下:
|
|
std::add_rvalue_reference_t<T>是类型特征(type trait),用于将类型T转换为右值引用T&&。noexcept表示该函数不会抛出异常。
std::declval 的主要作用是在编译时生成一个类型的右值引用,而不需要实际构造该类型的对象。这在以下场景中非常有用:
- 类型推导:在模板元编程中,推导某个表达式的类型。
- SFINAE:检查某个类型是否支持特定操作(例如成员函数、操作符等)。
- 编译时表达式检查:在不实际构造对象的情况下,检查某个表达式是否合法。
例子:
|
|
最终实现insert
|
|
通过SFINAE,只有支持*和++操作的InputIt才会走进这个函数,从而解决最初的那个问题。