What exactly is “broken” with Microsoft Visual C++

2019-01-01 09:39发布

问题:

Reading questions, comments and answers on SO, I hear all the time that MSVC doesn\'t implement two-phase template lookup / instantiation correctly.

From what I understand so far, MSVC++ is only doing a basic syntax check on template classes and functions and doesn\'t check that names used in the template have atleast been declared or something along those lines.

Is this correct? What am I missing?

回答1:

I\'ll just copy an example from my \"notebook\"

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\'
}

The above code is supposed to compile in a standard C++ compiler. However, MSVC (2005 as well as 2010 Express) will report an error because of incorrect implementation of two-phase lookup.


And if you look closer, the issue is actually two-layered. At the surface, it is the obvious fact that Microsoft\'s compiler fails to perform early (first phase) lookup for a non-dependent expression foo(0). But what it does after that does not really behave as a proper implementation of the second lookup phase.

The language specification clearly states that during the second lookup phase only ADL-nominated namespaces get extended with additional declarations accumulated between the point of definition and point of instantiation. Meanwhile, non-ADL lookup (i.e. ordinary unqualified name lookup) is not extended by the second phase - it still sees those and only those declarations that were visible at the first phase.

That means that in the above example the compiler is not supposed to see void foo(int) at the second phase either. In other words, the MSVC\'s behavior cannot be described by a mere \"MSVC postpones all lookup till the second phase\". What MSVC implements is not a proper implementation of the second phase either.

To better illustrate the issue, consider the following example

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);
}

Note that even though bar(t) call inside the template definition is a dependent expression resolved at the second lookup phase, it should still resolve to void bar(void *). In this case ADL does not help the compiler to find void bar(N::S *s), while the regular unqualified lookup is not supposed to get \"extended\" by the second phase and thus is not supposed to see void bar(N::S *s) either.

Yet, Microsoft\'s compiler resolves the call to void bar(N::S *s). This is incorrect.

The problem is still present in its original glory in VS2015.



回答2:

The Clang project has a pretty good writeup of two-phase lookup, and what the various implementation differences are: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html

Short version: Two phase lookup is the name for the C++ standard defined behavior for name lookup within template code. Basically, some names are defined as dependent (the rules for which are a bit confusing), these names must be looked up when instantiating the template, and independent names must be looked up when parsing the template. This is both hard to implement (apparently), and confusing for developers, so compilers tend to not implement it to the standard. To answer your question, it looks like Visual C++ delays all lookups, but searches both the template context and the instantiation context, so it accepts a lot of code that the standard says it shouldn\'t. I\'m not sure whether it doesn\'t accept code it should, or worse, interprets it differently, but it seems possible.



回答3:

Historically gcc didn\'t implement the two-phase name lookup correctly either. It\'s apparently very difficult to get to, or at least there wasn\'t much incentive...

  • gcc 4.7 claims to implement it correctly, at last
  • CLang aims at implementing it, baring bugs, it\'s done on ToT and will get into 3.0

I don\'t know why VC++ writers never chose to implement this correctly, implementation of a similar behavior on CLang (for microsoft compabitility) hints that there might be some performance gain to delaying the instantiation of templates at the end of the translation unit (which does not mean implementing the look-up incorrectly, but make it even more difficult). Also, given the apparent difficulty of a correct implementation it may have been simpler (and cheaper).

I would note that VC++ is first, and foremost, a commercial product. It is driven by the need to satisfy its clients.



回答4:

short answer

Disable language extensions with /Za

longer answer

I was investigating this issue lately and was amazed that under VS 2013 following example from standard [temp.dep]p3 produces wrong result:

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;
}

will print:

type of a: int

while it should print double. The solution to make VS standard conformant is to disable language extensions (option /Za), now type of x.a will resolve to double, also other cases of using dependent names from base classes will be standard conformant. I am not sure if this enables two phase lookup.



回答5:

Now that MSVC has most of two-phase name lookup implemented, I\'m hoping this blog post answers this question completely: Two-phase name lookup comes to MSVC (VC++ blog)