Microsoft Visual C ++的两阶段模板实例化到底是什么“破碎”?

因此,我一直听到MSVC没有正确实现两阶段模板查找/实例的问题。

从我目前了解的情况来看,MSVC ++只是对模板类和函数进行基本的语法检查,并不检查模板中使用的名称是否至less已被声明或沿着这些行进行了检查。

它是否正确? 我错过了什么?

我只是从我的“笔记本”复制一个例子

int foo(void*); template<typename T> struct S { S() { int i = foo(0); } // A standard-compliant compiler is supposed to // resolve the 'foo(0)' call here (ie early) and // bind it to 'foo(void*)' }; void foo(int); int main() { S<int> s; // VS2005 will resolve the 'foo(0)' call here (ie // late, during instantiation of 'S::S()') and // bind it to 'foo(int)', reporting an error in the // initialization of 'i' } 

上面的代码应该在标准的C ++编译器中编译。 但是,由于两阶段查找的错误实施,MSVC(2005以及2010 Express)将报告错误。


如果你仔细观察,这个问题实际上是两层的。 从表面上看,很显然,微软的编译器无法对非依赖expression式foo(0)进行早期(第一阶段)查找。 但是之后它所做的并不是第二次查找阶段的正确实现。

语言规范清楚地表明,在第二次查找阶段, 只有ADL提名的命名空间被扩展,并在定义点和实例化点之间累积了额外的声明。 同时,非ADL查找(即普通的非限定名称查找) 不会在第二阶段扩展 – 它仍然可以看到那些且仅在第一阶段可见的那些声明。

这意味着在上面的例子中编译器不应该在第二阶段看到void foo(int) 。 换句话说,MSVC的行为不能仅仅由“MSVC推迟到第二阶段的所有查找”来描述。 MSVC所实现的不是第二阶段的正确实施。

为了更好地说明问题,请考虑以下示例

 namespace N { struct S {}; } void bar(void *) {} template <typename T> void foo(T *t) { bar(t); } void bar(N::S *s) {} int main() { N::S s; foo(&s); } 

请注意,尽pipe模板定义中的bar(t)调用是在第二个查找阶段parsing的依赖expression式,但仍然应该parsing为void bar(void *) 。 在这种情况下,ADL不会帮助编译器findvoid bar(N::S *s) ,而常规的不合格查找不应该被第二阶段“扩展”,因此不应该看到void bar(N::S *s)

然而,微软的编译器解决了对void bar(N::S *s)的调用。 这是不正确的。

在VS2015中,问题依然存在。

铿锵项目有一个两阶段查找相当不错的写作,以及各种实现差异是: http : //blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html

简短版本:两阶段查找是模板代码中用于名称查找的C ++标准定义行为的名称。 基本上,有些名称被定义为依赖 (规则有点混乱),在实例化模板时必须查找这些名称,并且在parsing模板时必须查找独立的名称。 这显然是很难实现的,而且对于开发人员来说也是令人困惑的,所以编译器往往不会将其实现到标准。 为了回答你的问题,它看起来像Visual C ++延迟了所有的查找,但同时search模板上下文和实例化上下文,所以它接受了很多标准说不应该的代码。 我不确定它是否接受它应该的代码,或者更糟的是,它不同地解释它,但似乎是可能的。

历史上gcc没有正确实现两阶段名称查找。 这显然是非常困难的,或者至less没有太多的激励。

  • 海湾合作委员会4.7声称最终正确实施
  • CLang旨在实现它,禁止错误,它在ToT上完成,并将进入3.0

我不知道为什么VC ++编写者从来没有select正确实现这一点,在CLang上实现类似的行为(对于微软compabitility)暗示可能会有一些性能增益来延迟翻译单元末尾的模板实例化并不意味着错误地执行查找,但使其变得更加困难)。 另外,考虑到正确实施的明显困难,它可能更简单(并且更便宜)。

我会注意到VC ++是第一个,也是最重要的商业产品。 这是由满足其客户的需要驱动的。

简短的回答

使用/ Za禁用语言扩展

更长的答案

最近我正在调查这个问题,并惊奇的发现,在VS 2013下面的例子中,标准的[temp.dep] p3产生了错误的结果:

 typedef double A; template<class T> class B { public: typedef int A; }; template<class T> struct X : B<T> { public: A a; }; int main() { X<int> x; std::cout << "type of a: " << typeid(xa).name() << std::endl; } 

将打印:

 type of a: int 

而它应该打印double 。 VS标准符合的解决scheme是禁用语言扩展(选项/ ZA),现在types的XA将解决双倍,其他情况下使用从基类的相关名称将符合标准。 我不确定这是否能够进行两阶段查找。