Я просто скопирую пример из моей "записной книжки"
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'
}
Приведенный выше код должен компилироваться в стандартном компиляторе 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);
}
Обратите внимание, что хотя вызов bar(t)
внутри определения шаблона является зависимым выражением, разрешенным на втором этапе поиска, он все равно должен разрешиться до void bar(void *)
. В этом случае ADL не помогает компилятору найти void bar(N::S *s)
, в то время как обычный неквалифицированный поиск не должен «расширяться» на втором этапе и, следовательно, также не должен видеть void bar(N::S *s)
.
Тем не менее, компилятор Microsoft разрешает вызов void bar(N::S *s)
. Это неверно.
Проблема все еще присутствует в своем первоначальном виде в VS2015.