Xeo*_*Xeo 37 c++ templates instantiation visual-c++
在SO上阅读问题,评论和答案,我一直听说MSVC没有正确地实现两阶段模板查找/实例化.
据我所知,到目前为止,MSVC++只对模板类和函数进行了基本的语法检查,并没有检查模板中使用的名称是否至少被声明了或者沿着这些行.
它是否正确?我错过了什么?
AnT*_*AnT 41
我只是从我的"笔记本"中复制一个例子
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 (i.e. early) and
// bind it to 'foo(void*)'
};
void foo(int);
int main() {
S<int> s;
// VS2005 will resolve the 'foo(0)' call here (i.e.
// late, during instantiation of 'S::S()') and
// bind it to 'foo(int)', reporting an error in the
// initialization of 'i'
}
Run Code Online (Sandbox Code Playgroud)
上面的代码应该在标准的C++编译器中编译.但是,MSVC(2005以及2010 Express)将报告错误,因为两阶段查找的实现不正确.
如果你仔细观察,问题实际上是两层的.从表面上看,显而易见的事实是,Microsoft的编译器无法对非依赖表达式执行早期(第一阶段)查找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);
}
Run Code Online (Sandbox Code Playgroud)
请注意,即使bar(t)模板定义中的调用是在第二个查找阶段解析的依赖表达式,它仍应解析为void bar(void *).在这种情况下,ADL无法帮助编译器查找void bar(N::S *s),而常规的非限定查找不应该被第二阶段"扩展",因此也不应该看到void bar(N::S *s).
然而,微软的编译器解决了对它的调用void bar(N::S *s).这是不正确的.
这个问题仍然存在于VS2015的原始荣耀中.
Sim*_*han 21
Clang项目有一个非常好的两阶段查找,以及各种实现差异:http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html
简短版本:两阶段查找是模板代码中名称查找的C++标准定义行为的名称.基本上,一些名称被定义为依赖(其规则有点令人困惑),在实例化模板时必须查找这些名称,并且在解析模板时必须查找独立名称.这很难实现(显然),并且让开发人员感到困惑,因此编译器往往不会将其实现为标准.要回答你的问题,看起来Visual C++会延迟所有查找,但会搜索模板上下文和实例化上下文,因此它会接受标准认为它不应该的大量代码.我不确定它是否不接受它应该接受的代码,或者更糟糕的是,它以不同的方式解释它,但它似乎是可能的.
从历史上看,gcc也没有正确实现两阶段名称查找.这显然很难达到,或者至少没有太大的激励......
我不知道为什么VC++编写者从未选择正确实现这一点,在CLang上实现类似的行为(对于微软的兼容性)暗示在翻译单元末端延迟模板的实例化可能会有一些性能提升(并不意味着错误地实施查找,但使其更加困难).此外,鉴于正确实施的明显困难,它可能更简单(并且更便宜).
我会注意到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(x.a).name() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
将打印:
type of a: int
Run Code Online (Sandbox Code Playgroud)
虽然它应该打印double.使VS标准符合的解决方案是禁用语言扩展(选项/ Za),现在xa的类型将解析为double,而使用基类中的依赖名称的其他情况将是标准的符合.我不确定这是否能实现两相查找.