SFINAE 的模板专业化

Template specialization for SFINAE

我使用模板已经有一段时间了,但最近我遇到了一个奇怪的 SFINAE 模板编程。 我的问题是我们为什么要写 typename = {something} 作为 C++ 中的模板参数?

这就是 SFINAE 的工作原理 ;)

如您所知,您必须 "create a failure" 在模板声明中而不是在模板定义中。像这样:

template < typename X, typename = ... here is the code which may generate an error     during instantiation >
void Bla() {}

在声明中放置一些 "code" 的唯一机会是在模板参数列表或模板函数声明本身内部定义一些东西,例如:

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>();
}

可是这里"creating an substitution failure"的背景是什么?

诀窍就在后面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 给我们返回一个 bool 值,如果无效则转换为 0,如果有效则转换为任何正数。而创建 char[0] 只是 c++ 中的一个错误!因此,通过对 ! 表达式的否定,我们得到一个 "is int" 和一个 "is not int"。一旦它尝试创建一个大小为 0 的数组,这是失败的,模板将不会被实例化,一旦它生成类型 char[!0],这是一个有效的表达式。

作为最后一步:

... typename = ...

的意思是:会定义一个类型,这里没有模板参数名,因为参数本身后面用不到。你也可以这样写:

... typename X = ... 

但是X没有用到,留下吧!

总结:您必须提供一些代码,根据给定表达式的类型或值,这些代码是否会产生错误。表达式必须是函数声明的一部分或模板参数列表的一部分。 不允许 在 function/class 定义中添加错误,因为这将不再是 SFINAE 意义上的 "not an error"。

更新: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 有第二个模板参数用作 return 类型,如果它的第一个参数将是 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 仅使用枚举类型调用?嗯,很简单,我们找到了触发替换失败的方法!

如果我们检查 cppreference for std::enable_if 它说:

template< bool B, class T = void >
struct enable_if;

If B is true, std::enable_if has a public member typedef type, equal to T; otherwise, there is no member typedef.

这意味着,如果 std::is_enum<T>::valuetrueT 的枚举类型就是这种情况),那么 B 将是 true,因此type 将是一个有效类型,即指定的 int

但是,如果 std::is_enum::valuefalse,那么 B 将是 false,因此 type 甚至不存在。在这种情况下,代码试图使用 ::type 而它不存在,因此这是一个替换错误。因此 SFINAE 介入并将其从候选列表中删除。


我们使用 = 0 的原因是提供一个默认的模板参数值,因为我们实际上对使用这种类型或其值不感兴趣,我们只用它来触发 SFINAE。

此构造是一个 无名 模板参数,提供了一个默认参数。通常模板参数看起来像

typename <identifier> [ = <type> ]

但标识符可以省略。

您可以在 SFINAE 模板中经常看到这种构造,因为当且仅当某些条件成立或某些代码段有效时,它是启用专业化的便捷方式。所以经常可以看到

template <typename T, typename = std::enable_if<(some-constexpr-involving-T)>::type> ...

如果表达式的计算结果恰好为真,那么一切都很好:std::enable_if<true>::type 就是 void,我们有一个很好的工作模板专业化。

现在如果上面的表达式恰好为假,std::enable_if<false>没有一个名为type的成员,发生替换失败(SFINAE中的SF),不考虑模板特化.

至此,tge 参数的作用结束。它不用于模板本身的任何内容,因此不需要名称。