Имя метода совпадает с именем метода шаблона в С++

К моему удивлению, эта программа компилируется как в MSCV, так и в GCC:

class A
{
public:
  int add() { return 0; }
  template<typename T>
  T add() { return T(); }
};

int main() { 
  A a;
  a.add();
  a.add<int>();
  return 0; 
}

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

Я пробовал поиск в Google и просматривал последний черновик стандарта, но не смог найти ответ - это то же самое имя метода шаблона и обычный метод, который отличается только по типу возвращаемого типа, законным в С++, или компиляторы просто разрешительны?

Ответ 1

Это всегда было законным С++.

14.5.6/2:

Шаблон функции может быть перегружен другими шаблонами функций и с обычными (не шаблонными) функциями. Нормальная функция не связана с шаблоном функции (т.е. Никогда не считается специализацией), даже если она имеет то же имя и тип, что и потенциально сгенерированная спецификация шаблона функций.

При использовании синтаксиса "шаблон-идентификатор", такого как add<int>, рассматриваются только функции шаблонов с достаточным количеством параметров шаблона. Таким образом, a.add<int>() даже не смотрит, соответствует ли не шаблон add.

Когда идентификатор называет как обычную функцию, так и шаблон функции, компилятор попытается вывести аргументы шаблона для шаблона функции, чтобы получить специализированную функцию шаблона. Затем все простые функции и все специализированные функции шаблонов сравниваются с обычной логикой перегрузки функции. [См. 13.3.1/7.]

В вашем примере вызов a.add() не может вывести аргумент шаблона T для версии шаблона. Таким образом, единственной жизнеспособной функцией является перегрузка без шаблонов.

Также существует другое правило, которое возникает в аналогичной ситуации: если функция без шаблона и специализированная функция шаблона в противном случае были бы неоднозначной перегрузкой, выигрывает функция без шаблона. [Это правило находится в разделе 13.3.3, в середине определения того, что делает одну функцию лучше другой для заданного набора аргументов.]

class B
{
public:
  int f(int n) { return n+1; }

  template<typename T>
  T f(T n) { return n; }
};

int main() {
  B b;
  b.f(1);       // both are viable, non-template wins
  b.f<int>(1);  // only the template is viable
  return 0;
}

Это имеет смысл, потому что шаблон все еще может использоваться другими специализациями или явно используя угловые скобки < >. Поэтому перегрузка шаблона функции с помощью функции без шаблона похожа на добавление явной специализации, но с меньшим количеством головных болей.