Почему компилятор не может вывести тип шаблона из аргументов по умолчанию?
Я был удивлен, что следующий код привел к 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);
это разрешено сегодня, выполнив (среди прочего) следующие шаги:
- попытка вывести параметры шаблона для кандидатов (A) и (B); это не удается для (А), который поэтому устраняется.
- выполните разрешение перегрузки; (B) выбрано
- сформируйте вызов, создав экземпляр аргумента по умолчанию
чтобы вывести из аргумента по умолчанию, этот аргумент по умолчанию должен быть сам создан до завершения процесса дедукции. Это может привести к ошибкам вне контекста 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".
хорошо, если разница между перегрузками и дефолтом полностью скрыта для клиента.