什么决定了两个函数模板声明是否声明相同的模板,或者是否是同名的重载函数?
答案从3.5p9可以找到:
如果以下条件满足,则在不同作用域中声明的两个名称应指代同一变量、函数、类型、枚举器、模板或命名空间:
- 两个名称具有外部链接,或者两个名称具有内部链接并在同一翻译单元中声明;以及 - 两个名称引用同一命名空间的成员或者同一类的成员(不通过继承);以及 - 当两个名称代表函数时,函数的参数类型列表(8.3.5)相同;以及 - 当两个名称表示函数模板时,签名(14.5.6.1)相同。
非模板非成员函数的签名(1.3.17)如下所示:
在签名中,包括函数名、参数类型列表(8.3.5)和封装命名空间(如果有的话)。签名被用作名称修饰和链接的依据。
“parameter-type-list”已经被提到两次,它在第8.3.5p5节中定义。该段描述了实际函数参数的类型如何从声明的类型进行调整,将数组和函数替换为指针,并且舍弃顶级“cv限定符”。然后,
由转换后的参数类型组成的列表加上省略号或函数参数包的存在或缺失即为函数的“parameter-type-list”。
因此,在非模板情况下,“parameter-type-list”明显是一个类型的概念性语义列表(可能带有一些花哨的结尾),而不是标记序列或语法结构。下面的内容违反了ODR,因为两个定义定义了同一个函数:
在模板情况下,我们有(1.3.18):
签名:函数模板名称、参数类型列表(8.3.5)、封闭命名空间(如果有)、返回类型和模板参数列表。
现在考虑:
但是,这并不适用于上面的示例
答案从3.5p9可以找到:
如果以下条件满足,则在不同作用域中声明的两个名称应指代同一变量、函数、类型、枚举器、模板或命名空间:
- 两个名称具有外部链接,或者两个名称具有内部链接并在同一翻译单元中声明;以及 - 两个名称引用同一命名空间的成员或者同一类的成员(不通过继承);以及 - 当两个名称代表函数时,函数的参数类型列表(8.3.5)相同;以及 - 当两个名称表示函数模板时,签名(14.5.6.1)相同。
非模板非成员函数的签名(1.3.17)如下所示:
在签名中,包括函数名、参数类型列表(8.3.5)和封装命名空间(如果有的话)。签名被用作名称修饰和链接的依据。
“parameter-type-list”已经被提到两次,它在第8.3.5p5节中定义。该段描述了实际函数参数的类型如何从声明的类型进行调整,将数组和函数替换为指针,并且舍弃顶级“cv限定符”。然后,
由转换后的参数类型组成的列表加上省略号或函数参数包的存在或缺失即为函数的“parameter-type-list”。
因此,在非模板情况下,“parameter-type-list”明显是一个类型的概念性语义列表(可能带有一些花哨的结尾),而不是标记序列或语法结构。下面的内容违反了ODR,因为两个定义定义了同一个函数:
void f(int, int*) {}
void f(int p, decltype(p)*) {}
在模板情况下,我们有(1.3.18):
签名:函数模板名称、参数类型列表(8.3.5)、封闭命名空间(如果有)、返回类型和模板参数列表。
现在考虑:
template<typename T> void g(int, int*, T, T*) {} // #1
// template<typename T> void g(int p, decltype(p)*, T, T*) {} // #2
template<typename T> void g(int, int*, T q, decltype(q)*) {} // #3
使用g++ -std=c++0x版本4.6.3时,发现定义#1和#2定义了相同的函数,但是接受#1和#3作为重载没有问题。(它还认为#3比#1更专业,而且没有办法调用#1,但这是一个离题的问题。)#2和#3之间的主要区别在于q
是类型相关的,而p
则不是。因此,我猜decltype(q)
的含义直到实例化模板才能确定?这种行为是否由标准保证?
对于函数模板,必须允许parameter-type-list包括尚未被实例化替换的模板参数,因此涉及到依赖名称等问题。但如果可能的话,这会使得知道两个声明是否等价变得棘手。
14.5.6.1的第5-6段解决了类似的问题,它定义了equivalent表达式和equivalent函数模板声明(除了不同的声明可能使用不同的标识符表示模板参数之外,其令牌序列相同),functionally equivalent表达式和functionally equivalent函数模板声明(在实例化后相同),并要求:
如果程序包含功能上等价但不等价的函数模板声明,则程序是有问题的;不需要诊断。
第5段中的一个示例演示了安全等价的函数模板:
template <int I, int J> void f(A<I+J>); // #1
template <int K, int L> void f(A<K+L>); // same as #1
第七段中的例子展示了违反该规则的情况:
// Ill-formed, no diagnostic required
template <int I> void f(A<I>, A<I+10>);
template <int I> void f(A<I>, A<I+1+2+3+4>);
但是,这并不适用于上面的示例
g
函数。在某些类似类型等价定义下,T*
和decltype(q)*
可以被认为是功能等效的,但是14.5.6.1节只说明了表达式的替换,而不是类型的替换。