模板默认类型与默认值

wal*_*lly 5 c++ templates sfinae

这个问题是继上一个问题之后提出的。


情况一:默认类型

以下程序无法编译并报告error C2995: 'T foo(void)': function template has already been defined

#include <iostream>
#include <type_traits>

template < typename T, typename = std::enable_if_t< std::is_integral<T>::value> >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, typename = std::enable_if_t< !std::is_integral<T>::value> >
T foo() { std::cout << "non-integral" << std::endl; return T(); }

int main() {
    foo<int>();
    foo<float>();
}
Run Code Online (Sandbox Code Playgroud)

每个模板都由两个实例交替foo使用和忽略 (SFINAE) 。所以我假设编译器在某个时候会看到:

template < typename T, typename = void >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, typename = void >
T foo() { std::cout << "non-integral" << std::endl; return T(); }
Run Code Online (Sandbox Code Playgroud)

两个定义是相同的,并且错误在某种程度上是可以理解的。也许不太理解的是为什么编译器此时没有分配不同的内部函数名称。


情况2:默认值

现在,我们可以使用默认值来修复程序,而不是使用默认类型:

template < typename T, std::enable_if_t< std::is_integral<T>::value>* = nullptr >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, std::enable_if_t< !std::is_integral<T>::value>* = nullptr >
T foo() { std::cout << "non-integral" << std::endl; return T(); }
Run Code Online (Sandbox Code Playgroud)

在这里,按照相同的过程,我得出:

template < typename T, void* = nullptr >
T foo() { std::cout << "integral" << std::endl; return T(); }

template < typename T, void* = nullptr >
T foo() { std::cout << "non-integral" << std::endl; return T(); }
Run Code Online (Sandbox Code Playgroud)

如果是替换的话,它会有相同的定义并且不会编译。很明显编译器没有这样做,或者如果是的话,它也不会就此停止并最终得到如下结果:

int foo_int() { std::cout << "integral" << std::endl; return int(); }

float foo_float() { std::cout << "non-integral" << std::endl; return float(); }

int main() {
    foo_int();
    foo_float();
}
Run Code Online (Sandbox Code Playgroud)

为什么编译器在第二种情况下能够获得两个不同的函数,而不是第一种情况?

标准指定了什么算法来解释模板默认类型与默认值?

Bar*_*rry 5

在这里,按照相同的过程,我得出:

在那之前一切都是对的。您有两个函数模板(忽略默认值):

template < typename T, std::enable_if_t< std::is_integral<T>::value>*>
T foo();

template < typename T, std::enable_if_t< !std::is_integral<T>::value>*>
T foo();
Run Code Online (Sandbox Code Playgroud)

这两个非类型模板参数没有 type void*。它们的类型分别为std::enable_if_t<std::is_integral<T>::value>*std::enable_if_t<!std::is_integral<T>::value>*。这些不是同一类型。甚至不存在T在替换后它们是相同类型的 a 。

具体规则在[temp.over.link]:

如果包含表达式的两个函数定义满足单一定义规则 (3.2),则两个涉及模板参数的表达式被认为是等效的,但用于命名模板参数的标记可能不同,只要用于命名模板参数的标记相同即可。一个表达式被替换为另一个标记,该标记在另一个表达式中命名相同的模板参数。为了确定两个从属名称 (14.6.2) 是否等效,仅考虑名称本身,而不考虑模板上下文中名称查找的结果。

如果两个函数模板在同一作用域中声明、具有相同的名称、具有相同的模板参数列表,并且使用上述规则比较涉及模板参数的表达式具有等效的返回类型和参数列表,则这两个函数模板是等效的。如果两个函数模板等效,但返回类型和参数列表中涉及模板参数的一个或多个表达式在功能上等效(使用上述规则比较涉及模板参数的表达式),则两个函数模板在功能上等效。如果程序包含功能等效但不等效的函数模板声明,则该程序是格式错误的;无需诊断。

这两个函数没有相同的模板参数列表。