SFINAE的模板专业化

时间:2018-09-12 15:46:55

标签: c++ templates sfinae

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

3 个答案:

答案 0 :(得分:2)

这就是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定义的类型调用的。

答案 1 :(得分:1)

一个例子:

//for enum types.
template <typename T, typename std::enable_if<std::is_enum_v<T>, int>::type = 0>
void Foo(T value)
{
  //stuff..
}

如果Foo是枚举类型,我只希望选择T。我们可以使用“替代失败不是错误”(SFINAE)的规则来实现这一目标。


此规则指出,如果替换期间发生故障,则不应是编译错误。编译器只应消除此替换函数。

那么,如何确保仅使用枚举类型调用Foo?好了,这很容易,我们找到了一种触发替换失败的方法!

如果我们选中cppreference for std::enable_if,它说:

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

如果Btrue,则std::enable_if具有公共成员typedef type,等于   到T;否则,没有成员typedef。

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

如果intstd::is_enum::value,则false将是B,因此false甚至将不存在。在这种情况下,代码尝试使用type而不存在的情况,因此这是替换错误。因此SFINAE介入并从候选人名单中将其删除。


我们使用::type的原因是提供默认的模板参数值,因为我们实际上对使用此类型或其值并不感兴趣,我们仅使用它来触发SFINAE。

答案 2 :(得分:1)

此构造是带有默认参数的 nameless 模板参数。通常,模板参数看起来像

typename <identifier> [ = <type> ]

但标识符可以省略。

您经常会在SFINAE模板中看到这种构造,因为当且仅当满足某些条件或某些代码有效时,这是启用专门化的便捷方法。因此,人们经常可以看到

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

如果表达式恰好等于true,那么一切都很好:std::enable_if<true>::type只是void,我们有一个很好的工作模板专业化能力。

现在,如果上面的表达式碰巧是假的,std::enable_if<false>没有名为type的成员,则会发生替换失败(SFINAE中的SF),并且不考虑模板的特殊化。 / p>

tge参数的作用到此结束。模板本身中没有使用它,因此不需要名称。