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

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

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

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

Ответ 1

Я просто скопирую пример из "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'
}

Вышеприведенный код должен компилироваться в стандартном компиляторе С++. Тем не менее, 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.

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

Отключить языковые расширения с помощью /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), теперь тип xa будет удваиваться, также другие случаи использования зависимых имен от базовых классов будут стандартными совместимыми. Я не уверен, если это позволяет двухфазный поиск.

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