Необходимость typename в глобальной функции и функции-члене

template <class T>
T f(T::X x);

template <class T>
struct A {
  T f(T::X x);
};

Почему первая функция не компилируется без typename, а вторая компилируется https://godbolt.org/z/5x7r8WfGs?

error: expected ')' before 'x'


Ответы (2 шт):

Автор решения: AR Hovsepyan

Компилятору не известен тип T::X. Вот если вы напишете typename T::X, то это будет подсказкой, что эта функция принимает в аргумент именно такой тип, а потом вы увидите, что вторая функция тоже не компилируется. Не сообщать же вам о той же самой ошибки два раза... Наверняка ваш вопрос стал для кого то плохим за то, что вы уже получили от компилятора сообщение.

P.S. Правильно заметил пользователь Qwertiy, что ответ лучше дополнить для полного введения ясности:

Нам известно, что С++, это язык строгой типизации. По этой причине, компиляторы С++ всегда должны иметь возможность однозначно определить синтаксический смысл каждой инструкции на ранней стадии трансляции шаблона(первая фаза). Пока не известны параметры шаблона(шаблон не инстанцирован), природа зависимых имен неизвестна. А в контексте T::X, квалифицированное имя Xявляется зависимым именем,и не известно что она из себя представляет. Из за этого могут возникать не разрешимые неоднозначности. Такого рода неоднозначности, разрешаются при помощи явного указания ключевого слова typename точно также, как и для шаблонной функции члена нужно указать, что это имя шаблона. Например:

A::template foo<>();

Теперь о том, почему некоторые компиляторы могут нормально воспринять:

template <class T>
struct A {
  T f(T::X x);
};

без указания typename:

Старые версии компилятора MSVC, двухфазный поиск имен реализовали иначе(не правильно), и для них такая запись не означала возможное возникновение проблем с неоднозначностью. Поэтому эти компиляторы(их "производные"), могли пропустить вышеуказанный код. Но по мере столкновения с проблемами, дефект убрали.

Но тут есть еще и другой момент. В примере со структурой, очевидно, что f является функцией_членом, и не может принимать в аргумент не тип. Поэтому в С++20 для подобных случаев сделали исключение, и можно не указывать ключевое слово.

А вот в примере:

template <class T>
T f(T::X x); 

это может вполне быть определение объекта, получающее значение T::X x, но тогда не понятно что из себя представляет это выражение, поэтому возникает неоднозначность.

Чтобы избежать непереносимости кода и для уверенности _ лучше написать ключевое слово для квалифицированных имен. Также, как и лучше написать скобки вокруг, когда не уверены в приоритетах операций.

→ Ссылка
Автор решения: HolyBlackCat

Это нововведение C++20, необязательный typename в некоторых местах (где и так понятно, что это тип). В том числе необязательный в типах параметров функций-членов, но не глобальных функций.

Обратите внимание, что если бы какой-то компилятор не выдавал ошибку на вашем коде (в первом случае, или во втором до С++20), то это не было бы багом, потому что от компилятора не требуется проверять неинстанцированные шаблоны.

→ Ссылка