Почему компилятор не может вывести тип шаблона из аргументов по умолчанию?

Я был удивлен, что следующий код привел к could not deduce template argument for T ошибка:

struct foo
{
  template <typename T>
  void bar(int a, T b = 0.0f)
  {
  }
};

int main()
{
  foo a;
  a.bar(5);

  return 0;
}

вызов a.bar<float>(5) устраняет проблему. Почему компилятор не может вывести тип из аргумента по умолчанию?

3 ответов


в C++03 спецификация явно запрещает использование аргумента по умолчанию для вывода аргумента шаблона (C++03 §14.8.2/17):

шаблон тип-параметр невозможно вывести из типа аргумента функции по умолчанию.

В C++11 можно указать аргумент шаблона по умолчанию для шаблона функции:

template <typename T = float>
void bar(int a, T b = 0.0f) { }

однако требуется аргумент шаблона по умолчанию. Если шаблон по умолчанию аргумент не предоставляется, аргумент функции по умолчанию по-прежнему не используется для вывода аргумента шаблона. В частности, применяется следующее (C++11 14.8.2.5 / 5):

не выводил контексты:

...

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

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

template<typename T, typename U> void f(U p = T::g());  // (A)
template<typename T> T f(long, int = T());  // (B)
int r = f<int>(1);

это разрешено сегодня, выполнив (среди прочего) следующие шаги:

  1. попытка вывести параметры шаблона для кандидатов (A) и (B); это не удается для (А), который поэтому устраняется.
  2. выполните разрешение перегрузки; (B) выбрано
  3. сформируйте вызов, создав экземпляр аргумента по умолчанию

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


хорошей причиной может быть то, что

void foo(bar, xyzzy = 0);

похоже на пару перегрузок.

void foo(bar b) { foo(b, 0);  }
foo(bar, xyzzy);

более того, иногда это выгодно, чтобы переделать его в такого:

void foo(bar b) { /* something other than foo(b, 0); */ }
foo(bar, xyzzy);

даже когда написано как один, это все равно как две функции в одном, ни одна из которых не "предпочтительна" в любом смысле. Вы вызываете один-аргумент функции; двух-аргумент один-это по сути разные функции. Нотация аргументов по умолчанию просто объединяет их в один.

Если перегрузка должна была иметь поведение, которое вы просите, то для согласованности она должна была бы работать в случае, когда шаблон разделен на два определения. Это не имеет смысла, потому что тогда дедукция будет вытягивать типы из несвязанной функции, которая не вызывается! И если бы он не был реализован, это означало бы, что перегрузка разных длин списка параметров становится "гражданином второго класса" по сравнению с "default-argumenting".

хорошо, если разница между перегрузками и дефолтом полностью скрыта для клиента.