我已经使用模板有一段时间了,但最近我遇到了一个奇怪的SFINAE模板编程。 我的问题是我们为什么要写typename = {something}
作为C++中的模板参数?
这就是SFINAE的工作方式;)
如您所知,您必须在模板声明中"创建失败",而不是在模板定义中。如:
template < typename X, typename = ... here is the code which may generate an error during instantiation >
void Bla() {}
在声明中放置一些"代码"的唯一机会是在模板参数列表或模板函数声明本身中定义一些内容,例如:
template < typename X>
void Bla( ... something which may generates an error ) {}
例:
template <typename T, typename = std::enable_if_t< std::is_same_v< int, T>>>
void Bla()
{
std::cout << "with int" << std::endl;
}
template <typename T, typename = std::enable_if_t< !std::is_same_v< int, T>>>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}
int main()
{
Bla<int>();
Bla<std::string>();
}
但这里"制造换人失败"的背景是什么?
诀窍在std::enable_if
后面的某个地方.我们也可以在没有它的情况下使用它:
template <typename T, typename = char[ std::is_same_v< int, T>]>
void Bla()
{
std::cout << "with int" << std::endl;
}
template <typename T, typename = char[ !std::is_same_v< int, T>]>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
}
看看:typename = char[ !std::is_same_v< int, T>]
在这里,std::is_same_v
给了我们一个布尔值,如果无效,则转换为 0,如果有效,则转换为任何正数。创建char[0]
只是 c++ 中的一个错误!因此,通过用!
否定我们的表达式,我们得到了一个"is int"和一个"is not int"。一旦它尝试创建一个大小为 0 的数组,这是一个失败,模板不会被实例化,一旦它生成了有效表达式的类型char[!0]
。
最后一步:
... typename = ...
意思是:将定义一个类型,这里没有模板参数名称,因为参数本身以后不会使用。你也可以写:
... typename X = ...
但由于不使用 X,请离开它!
摘要:您必须提供一些代码,这些代码是否会生成错误,具体取决于给定表达式的类型或值。表达式必须是函数声明的一部分或模板参数列表的一部分。不允许在函数/类定义中放置错误,因为这不再是 SFINAE 意义上的"非错误"。
更新:SFINAE 表达式的结果可以用于进一步的表达式吗:是
例:
template < typename TYPE >
void CheckForType()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template <typename T, typename X = std::enable_if_t< std::is_same_v< int, T>, float>>
void Bla()
{
std::cout << "with int" << std::endl;
CheckForType<X>();
}
template <typename T, typename X = std::enable_if_t< !std::is_same_v< int, T>, double >>
void Bla(int=0)
{
std::cout << "with something else" << std::endl;
CheckForType<X>();
}
int main()
{
Bla<int>();
Bla<std::string>();
}
输出:
with int
void CheckForType() [with TYPE = float]
with something else
void CheckForType() [with TYPE = double]
解释:std::enable_if
有第二个模板参数用作返回类型,如果它的第一个参数将true
。因此,您可以在此处使用该类型。如您所见,函数CheckForType
是使用此定义的类型从 SFINAE 表达式中X
调用的。
一个例子:
//for enum types.
template <typename T, typename std::enable_if<std::is_enum_v<T>, int>::type = 0>
void Foo(T value)
{
//stuff..
}
我只希望在T
是枚举类型时选择此Foo
。我们可以使用替代失败不是错误(SFINAE(的规则来实现这一点。
此规则指出,如果在替换过程中出现故障,则不应是编译错误。编译器应仅消除此替换的函数。
那么,我们如何确保仅使用枚举类型调用Foo
呢?嗯,这很容易,我们找到了触发替换失败的方法!
如果我们检查 cpp首选项std::enable_if
它说:
template< bool B, class T = void >
struct enable_if;
如果
B
true
,则std::enable_if
有一个公共成员类型定义type
,等于 到T
;否则,没有成员类型定义。
这意味着,如果std::is_enum<T>::value
true
(T
的枚举类型就是这种情况(,那么B
将被true
,因此type
将是一个有效的类型,一个指定的int
。
但是,如果std::is_enum::value
false
那么B
将被false
,因此type
甚至不存在。在这种情况下,代码试图在::type
不存在的情况下使用它,因此这是一个替换错误。因此,SFINAE启动并将其从候选列表中删除。
我们使用= 0
的原因是提供一个默认的模板参数值,因为我们实际上对使用此类型或其值并不感兴趣,我们只使用它来触发 SFINAE。
此构造是随默认参数提供的无名称模板参数。通常,模板参数如下所示
typename <identifier> [ = <type> ]
但可以省略标识符。
您可以在 SFINAE 模板中经常看到这种构造,因为当且仅当某些条件成立或某些代码段有效时,这是一种启用专用化的便捷方法。因此,人们经常可以看到
template <typename T, typename = std::enable_if<(some-constexpr-involving-T)>::type> ...
如果表达式碰巧计算为 true,则一切都很好:std::enable_if<true>::type
只是void
,我们有一个很好的工作模板专业化。
现在,如果上面的表达式恰好为 false,std::enable_if<false>
没有名为type
的成员,则会发生替换失败(SFINAE 中的 SF(,并且不考虑模板专用化。
tge参数的作用到此结束。它不用于模板本身中的任何内容,因此它不需要名称。