我最近创建了这个示例代码来说明C++11可变模板函数的用法。
template <typename Head, typename... Tail> void foo (Head, Tail...);
template <typename... Tail> void foo (int, Tail...);
void foo () {}
template <typename... Tail>
void foo (int x, Tail... tail)
{
std :: cout << "int:" << x;
foo (tail...);
}
template <typename Head, typename... Tail>
void foo (Head x, Tail... tail)
{
std :: cout << " ?:" << x;
foo (tail...);
}
foo (int (123), float (123)); // Prints "int:123 ?:123.0"
如果前向声明foo
的前两行被省略,则这将打印int:123int:123
。这让一位经验丰富、知识渊博的C++程序员感到惊讶。
他确信正向声明不应该是必要的,因为在两阶段查找的第二阶段之前,主体不会被实例化。他认为编译器(gcc 4.6(有一个错误。
我相信编译器是对的,因为两个foo
是不同的基础模板函数,基础模板的选择需要在第一阶段锁定,否则,在定义foo
的所有版本之前实例化它,然后再实例化它,可能会违反一个定义规则(考虑链接器如何假设冗余模板函数定义是相同的、可互换的和可丢弃的(。
那么,谁是对的呢?
上面链接的GOTW很好地解释了函数模板如何以及为什么不部分专业化,但可变模板函数的存在似乎增加了混乱——至少对我来说,foo<int,Tail...>
应该是foo<Head,Tail...>
的部分专业化的直觉比非可变函数的直觉更强
GCC(和Clang(是对的。MSVC会出错,因为它没有正确地实现查找。
你的同事似乎有误解。查找规则为:
- 在从定义调用基本模板函数之前,需要声明该函数
- Specialized模板函数需要在实例化之前声明
注意:这些规则适用于自由函数,在类中不需要前向声明
请注意,因为定义也充当声明,所以在您的示例中,没有必要转发声明int
版本。
正确示例:
template <typename T> void foo(T); // declare foo<T>
template <typename T> void bar(T t) { foo(t); }// call foo<T> (dependent context)
template <> void foo<int>(int); // declare specialiaztion foo<int>
void bar(int i) { foo(i); } // instantiate foo<T> with int
// which is the specialization
如果有可用的基本模板,则这是一个错误。如果在实例化之前没有声明专门化,那么就不会使用它,这可能意味着违反了ODR规则(如果另一个实例化使用了专门化(。
来自标准(C++0x FDIS(:
1.4.6.4.2
1.对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1,3.4.2,3.4.3(查找候选函数,但以下情况除外:
--对于使用非限定名称查找(3.4.1(或限定名称查找的查找部分(3.4.3(,只找到模板定义上下文中的函数声明。
--对于使用关联名称空间的查找部分(3.4.2(,只找到在模板定义上下文或模板实例化上下文中找到的函数声明。
如果函数名称是一个不合格的id,并且如果在相关名称空间内的查找考虑了所有转换单元中在这些名称空间中引入的具有外部链接的所有函数声明,而不仅仅考虑在模板定义和模板实例化上下文中发现的那些声明,则调用将是格式错误的或者会找到更好的匹配,则程序具有未定义的行为。
请注意,所提及的段落是关于常规职能的。
两相查找将找到:
- 在定义点可见的功能,以及
- ADL在实例化时可以找到的函数
ADL找不到template <typename Head, typename... Tail> void foo (Head x, Tail... tail)
,所以如果在定义时不可见,就根本找不到它。
换句话说,GCC是对的。