Почему компилятор не может определить тип шаблона из аргументов по умолчанию? - PullRequest
17 голосов
/ 09 марта 2012

Я был удивлен, что следующий код привел к ошибке 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 ]

18 голосов
/ 09 марта 2012

В 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):

Не выведенные контексты:

...

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

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

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); это не работает для (A), который поэтому исключается.
  2. выполнить разрешение перегрузки; (B) выбран
  3. Форма вызова, создание аргумента по умолчанию

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

4 голосов
/ 09 марта 2012

Хорошая причина может быть в том, что

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);

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

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

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

...