例如,以下程序工作正常.
#include <iostream> template<typename T> T foo(T t); int main() { foo(1); } template<typename T> T foo(T t) { std::cout << "A\n"; return 0; }
如果我改变模板定义中使用模板参数的方式,名称显然不再引用相同的模板,并且链接失败.
#include <iostream> template<typename T> T foo(T t); int main() { foo(1); } template<typename T> int foo(T t) { std::cout << "A\n"; return 0; } // or template<typename T> struct identity { typedef T type; }; template<typename T> typename identity<T>::type foo(T t) { std::cout << "A\n"; return 0; }
接下来,如果我将模板定义移动到另一个翻译单元,为了实现C(MSVC 11 beta),无论我怎么说这些类型,程序都能正常工作.
//main.cpp template<typename T> T foo(T t); int main() { foo(1); } //definition.cpp #include <iostream> template<typename T> struct identity { typedef T type; }; template<typename T> typename identity<T>::type foo(T t) { std::cout << "A\n"; return 0; } template int foo<int>(int);
要么
//definition.cpp #include <iostream> template<typename T> int foo(T t) { std::cout << "A\n"; return 0; } template int foo<int>(int);
或者即使定义根本不是模板:
//definition.cpp #include <iostream> int foo(T t) { std::cout << "A\n"; return 0; }
显然链接是成功的,因为签名/受损名称是相同的,无论实例化创建符号的模板如何.我认为这种未定义的行为是因为我违反了:
§ 14.1 [temp] p6
A function template,member function of a class template,or static
data member of a class template shall be defined in every translation
unit in which it is implicitly instantiated (14.7.1) unless the
corresponding specialization is explicitly instantiated (14.7.2) in
some translation unit; no diagnostic is required.
但后来说我试图通过在第二个翻译单元中放置模板的定义来满足这些要求,并在两个位置之一处包含一个显式实例化:
#include <iostream> template<typename T> T foo(T t) { std::cout << "A\n"; return 0; } // Location 1 template<typename T> int foo(int t) { std::cout << "B\n"; return 0; } // Location 2
有关消除显式实例化所指的模板歧义的规则是什么?将它放在位置1会导致正确的模板被实例化,并且该定义将在最终程序中使用,而将它放在位置2则实例化另一个模板,并导致我认为在上面的14.1 p6下未定义的行为.
另一方面,无论如何,两个模板定义的隐式实例化都会选择第一个模板,因此在这些情况下,消除模板歧义的规则似乎不同:
#include <iostream> template<typename T> T foo(T t) { std::cout << "A\n"; return 0; } template<typename T> int foo(int t) { std::cout << "B\n"; return 0; } int main() { foo(1); // prints "A" }
这出现的原因与this question有关,其中提问者发现了一个单一的前方声明
template<typename T> T CastScriptVarConst(const ScriptVar_t& s);
不能作为多个模板定义的声明:
template<typename T> typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type CastScriptVarConst(const ScriptVar_t& s) { return (T) s; } template<typename T> typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ) && std::is_base_of<CustomVar,T>::value,T>::type CastScriptVarConst(const ScriptVar_t& s) { return *s.as<T>(); }
我想更好地理解模板定义和声明之间的关系.
解决方法
template<class T> T foo(T t);
foo是模板名称.对于函数模板,决定它们是否相同的规则很长,如14.5.5.1“函数模板重载”中所述.该部分的第6段(我在这里引用C 03,因此C 11中的措辞和段落编号可能已经改变)在应用于涉及模板参数的表达式时定义等效和功能等同的术语.
简而言之,除了可能具有不同的模板参数名称之外,等效表达式是相同的,并且如果它们恰好评估相同的事物,则功能上等同的表达式是相同的.例如,前两个f声明是等效的,但第三个声明仅在功能上等同于另外两个: –
template<int A,int B> void f(array<A + B>); template<int T1,int T2> void f(array<T1 + T2>); template<int A,int B> void f(array< mpl::plus< mpl::int<A>,mpl::int<B> >::value >);
它继续在第7段中将这两个定义扩展到整个函数模板.匹配(在名称,范围和模板参数列表中)的两个函数模板是等效的,如果它们也具有等效的返回类型和参数类型,或者如果它们仅具有功能等效的返回类型和参数类型,则在功能上等效.看看你的第二个例子,这两个函数在功能上是等价的: –
template<typename T> T foo(T t); template<typename T> typename identity<T>::type foo(T t);
第7段以可怕的警告结束,“如果一个程序包含功能模板的声明,这些功能模板功能相同但不相同,则该程序格式不正确;不需要诊断.”因此,你的第二个例子是无效的C.检测这样的错误需要在二进制文件中注释函数模板的每个声明和定义,并使用AST描述每个参数和返回类型来自的模板表达式,这就是标准不需要实现来检测它的原因. MSVC有理由在编译你的第三个例子时如何打算,但这样做是合理的.
继续进行显式实例化,重要的部分是14.7,“模板实例化和专业化”.第5段不允许以下所有情况:
>多次显式实例化模板;
>明确地实例化并明确地专门化相同的模板;
>多次专门为同一组参数设置模板.
同样,“不需要诊断”,因为它很难被发现.
因此,为了扩展您的显式实例化示例,以下代码违反了第二条规则并且是非法的: –
/* Template definition. */ template<typename T> T foo(T t) { ... } /* Specialization,OK in itself. */ template< > int foo(int t) { ... } /* Explicit instantiation,OK in itself. */ template< > int foo(int t);
无论显式专业化和显式实例化的位置如何,这都是非法的,但当然因为不需要诊断,您可能会在某些编译器上获得有用的结果.还要注意显式实例化和显式特化之间的区别.以下示例格式错误,因为它声明了一个显式的特化而没有定义它: –
template<typename T> T f(T f) { ... } template< > int f(int); void g(void) { f(3); }
但是这个例子格式正确,因为它有一个明确的实例化: –
template<typename T> T f(T f) { ... } template f(int); void g(void) { f(3); }
< >完全不同.另请注意,即使您确实定义了显式特化,也必须在使用它之前,否则编译器可能已经为该模板生成了隐式实例化.这是在14.7.3“显式专业化”第6段,就在您正在阅读的地方下方,同样,不需要诊断.为了适应同样的例子,这是不正确的: -
template<typename T> T f(T f) { ... } void g(void) { f(3); } // Implicitly specializes int f(int) template< > int f(int) // Too late for an explicit specialization { ... }
如果你还不够困惑,请看看你的最后一个例子: –
template<typename T> T foo(T t) { ... } template<typename T> int foo(int t) { ... }
foo的第二个定义不是第一个定义的特化.它必须是模板< > int foo(int)是模板的特化< typename T> T foo(T).但是没关系:允许函数重载,并且允许在函数模板和普通函数之间使用. foo(3)形式的调用将始终使用第一个定义,因为其模板参数T可以从参数类型推导出来.第二个定义不允许从参数类型推导出其模板参数.只有明确指定T才能达到第二个定义,只有当调用与第一个定义不一致时:
f<int>(3); // ambiguous f<string>(3); // can only be the second one
对函数模板进行重载解析的整个过程太长了,无法在此描述.如果您有兴趣并阅读更多问题,请阅读第14.8.3节:-)