Что именно «нарушено» с помощью двухфазного экземпляра шаблона Microsoft Visual C ++?

Чтение вопросов, комментариев и ответов на SO, я все время слышу, что MSVC не реализует двухфазный поиск / создание шаблона правильно.

Из того, что я понимаю до сих пор, MSVC ++ выполняет только базовую проверку синтаксиса в classах и функциях шаблонов и не проверяет, чтобы имена, используемые в шаблоне, были объявлены или что-то вроде этих строк.

Это верно? Что мне не хватает?

Я просто скопирую пример из моей «записной книжки»,

int foo(void*); template 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 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) сообщит об ошибке из-за неправильной реализации двухфазного поиска.


И если вы посмотрите ближе, проблема на самом деле двухслойная. На первый взгляд очевидным является тот факт, что компилятор Microsoft не выполняет ранний (первый этап) поиск не зависящего выражения foo(0) . Но то, что он делает после этого, действительно не ведет себя как правильная реализация второй фазы поиска.

Спецификация языка четко заявляет, что на втором этапе поиска только расширенные пространства имен ADL расширяются с дополнительными декларациями, накопленными между точкой определения и точкой инстанцирования. Между тем, не-ADL-поиск (т. Е. Обычный неквалифицированный поиск имени) не расширяется второй фазой – он все еще видит те и только те объявления, которые были видны на первом этапе.

Это означает, что в приведенном выше примере компилятор не должен видеть void foo(int) на второй фазе. Другими словами, поведение MSVC не может быть описано просто «MSVC откладывает весь поиск до второй фазы». То, что MSVC реализует, не является надлежащей реализацией второй фазы.

Чтобы лучше проиллюстрировать эту проблему, рассмотрим следующий пример

 namespace N { struct S {}; } void bar(void *) {} template  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.

Проект Clang имеет неплохую запись двухфазного поиска и различные различия в реализации: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html

Краткая версия: двухэтапный поиск – это имя для стандартного поведения C ++ для поиска имени в коде шаблона. В основном, некоторые имена определяются как зависимые (правила для которых немного запутываются), эти имена необходимо искать при создании экземпляра шаблона, и при parsingе шаблона необходимо искать независимые имена. Это сложно реализовать (по-видимому) и запутать для разработчиков, поэтому компиляторы, как правило, не применяют его к стандарту. Чтобы ответить на ваш вопрос, похоже, что Visual C ++ задерживает все поисковые запросы, но ищет как контекст шаблона, так и контекст контекста, поэтому он принимает много кода, который, по словам стандарта, не должен. Я не уверен, что он не принимает код, который он должен , или, что еще хуже, интерпретирует его по-другому, но это кажется возможным.

Исторически gcc также не реализовал поиск двухфазного имени. По-видимому, очень трудно добраться, или, по крайней мере, не было большого стимула …

  • gcc 4.7 утверждает, что правильно его осуществил
  • CLAN нацелен на его реализацию, устраняя ошибки, это делается на ToT и попадает в 3.0

Я не знаю, почему авторы VC ++ никогда не пытались реализовать это правильно, реализация аналогичного поведения на CLang (для совместимости с Microsoft) подсказывает, что может быть некоторое увеличение производительности для задержки создания шаблонов в конце единицы перевода (что не означает, что реализация искажалась неправильно, но сделать ее еще более сложной). Кроме того, учитывая кажущуюся трудность правильной реализации, возможно, было проще (и дешевле).

Я хотел бы отметить, что VC ++ является первым и, прежде всего, коммерческим продуктом. Это обусловлено необходимостью удовлетворения своих клиентов.

короткий ответ

Отключить языковые расширения с помощью / Za

более длинный ответ

Я изучал этот вопрос в последнее время и был поражен тем, что в соответствии с VS 2013 следующий пример со стандартного [temp.dep] p3 вызывает неправильный результат:

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

будет печатать:

 type of a: int 

в то время как он должен печатать double . Решение для стандартного соответствия VS заключается в отключении языковых расширений (опция / Za), теперь тип xa будет удваиваться, а другие случаи использования зависимых имен из базовых classов будут стандартными. Я не уверен, что это позволяет осуществлять двухфазный поиск.

Теперь, когда MSVC имеет большую часть двухфазного поиска имени, я надеюсь, что это сообщение в блоге полностью ответит на этот вопрос: поиск в двух фазах начинается с MSVC (блог VC ++)

Давайте будем гением компьютера.