轨道上的轻盈竞赛 cited 为什么不合规标准中的零件。附近可能还有其他一些。
我将尝试用更简单的术语解释标准措辞的含义,希望我能正确理解它,最后解释链接器错误(或没有错误):
- 实例化的重点是什么?
- 编译器如何选择专业化?
- 实例化时需要什么?
- 为什么会出现链接器错误?
1/ 实例化的意义是什么?
模板函数的实例化点是调用或引用它的点(&std::sort<Iterator>
) with all模板参数充实了 (*)。
template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }
int main() { foo(1); } // point of instantiation of "foo<int>(int)"
但对于从其他模板调用的模板,它可能会延迟,因此与确切的调用站点不匹配:
template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }
template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"
int main() { foo(1); } // point of instantiation of "bar<int>(int)"
// and ALSO of "foo<int>(int)"
这种延迟非常重要,因为它可以写入:
(*) 粗略地说,也有一些例外,例如模板类的非模板方法......
2/编译器如何选择专业化?
在实例化时,编译器需要能够:
- 决定哪个基础模板要调用的函数
- 以及可能,它的哪个专业被称为
这个老GotW展示了专业化的困境......但简而言之:
template <typename T> void foo(T); // 1
template <typename T> void foo(T*); // 2
are 超载,并且每个都会产生一个独特的family他们是可能的专业base.
template <> void foo<int>(int);
是 1 的专业化,并且
template <> void foo<int*>(int*);
是2的专业。
为了解析函数调用,编译器会首先选择最好的重载,同时忽略模板专业化,然后,如果它选择了模板函数,请检查它是否有任何可以更好应用的专业化。
3/ 实例化时需要什么?
因此,从编译器解析调用的方式来看,我们可以理解why该标准规定任何专业化都应声明before它的第一个实例化点。否则的话,根本就不会考虑。
因此,在实例化时,人们需要已经看到:
- 要使用的基本模板函数的声明
- 要选择的专业声明(如果有)
但定义又如何呢?
不需要。编译器假设它将稍后在 TU 中提供或完全由另一个 TU 提供。
注意:它确实给编译器带来了负担,因为这意味着它需要记住它遇到的所有隐式实例化,并且它无法发出函数体,以便当它最终遇到定义时,它可以(最后)发出所有必要的代码它遇到的所有专业化。我想知道为什么选择这种特殊的方法,也想知道为什么即使没有extern
声明 TU 可能以未定义的函数体结束。
4/ 为什么会出现链接器错误?
由于没有提供定义,gcc 相信您稍后会提供它,并且只是发出对未解析符号的调用。如果您碰巧链接到另一个提供此符号的 TU,那么一切都会很好,否则您将收到链接器错误。
由于 gcc 遵循安腾ABI我们可以简单地看看它是如何破坏符号的。事实证明,ABI 在破坏专业化和隐式实例化方面没有任何区别,因此
cls.f( asd );
calls _ZN3cls1fIPKcEEvT_
(它分解为void cls::f<char const*>(char const*)
)和专业:
template<>
void cls::f( const char* )
{
}
还生产_ZN3cls1fIPKcEEvT_
.
注意:我不清楚是否可以对显式专业化进行不同的修改。