Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

Требуется аргумент шаблона по умолчанию. Если аргумент шаблона по умолчанию не указан, аргумент функции по умолчанию все еще не используется для вывода аргумента шаблона. В частности, применяется следующее (С++ 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);

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

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

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

Ответ 3

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

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

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

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

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