Что именно «сломано» в двухфазной реализации шаблона Microsoft Visual C ++? - PullRequest
36 голосов
/ 08 июня 2011

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

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

Это правильно? Чего мне не хватает?

Ответы [ 5 ]

40 голосов
/ 08 июня 2011

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

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.

21 голосов
/ 08 июня 2011

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

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

8 голосов
/ 08 июня 2011

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

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

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

5 голосов
/ 16 декабря 2014

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

Отключить языковые расширения с помощью / 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;
}

напечатает:

type of a: int

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

[Обновление июль-2019] Это также верно для 2015 - https://rextester.com/YOH81784,, но VS2019 показывает правильно double. Согласно этой статье Поддержка поиска по двухфазному имени входит в MSVC , она была исправлена ​​с VS 2017.

3 голосов
/ 17 ноября 2017

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

...