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

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


person Samaursa    schedule 09.03.2012    source источник


Ответы (3)


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

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

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

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

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

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

...

  • Параметр шаблона, используемый в типе параметра параметра функции, который имеет аргумент по умолчанию, который используется в вызове, для которого выполняется вывод аргумента.
person James McNellis    schedule 09.03.2012
comment
Хотя ответ «Потому что так сказано в стандарте» является правильным ответом, было бы неплохо узнать его обоснование. - person Jesse Good; 09.03.2012
comment
Среди прочего, разные объявления функции могут объявлять разные аргументы по умолчанию (я почти уверен, что то же самое относится и к шаблонам функций). - person James McNellis; 09.03.2012
comment
@James: Нет, разные объявления не могут объявлять разные аргументы по умолчанию. Не допускается даже, чтобы несколько объявлений давали одно и то же значение по умолчанию одному и тому же аргументу. 8.3.6 говорит, что аргумент по умолчанию не должен переопределяться последующим объявлением (даже до того же значения). Конечно, это относится только к функциям, не являющимся шаблонами. Похоже, что для шаблонных функций аргументы по умолчанию могут быть предоставлены только в первоначальном объявлении. - person Ben Voigt; 03.09.2013
comment
Другой обходной путь, помимо аргумента шаблона по умолчанию, состоит в том, чтобы иметь вторую функцию, которая вызывает первую с соответствующим значением по умолчанию inline void bar(int a){ bar(a, 0.0f);}. - person Eponymous; 09.11.2017

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

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

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

person Daveed V.    schedule 04.09.2015
comment
Звучит разумно для меня. - person Cheers and hth. - Alf; 11.01.2017

Уважительной причиной может быть то, что

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

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

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

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

person Kaz    schedule 09.03.2012