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

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

В разделе 8.3.6.4 стандарта С++ указано, что

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

Но мой вопрос в том, почему для функций шаблона не допускается? В чем причина не допускать добавления аргументов по умолчанию в более поздние объявления в той же области для функций шаблона?

Рассмотрим эту программу, которая компилируется отлично. (функция без шаблона) (см. живую демонстрацию здесь.)

#include <iostream>

int f(int a,int b,int c=3);
int f(int a,int b=9,int c); // default argument in middle, ok allowed

int main()
{
    f(3);
    f(3,6);
    f(3,6,9);
    return 0;
}

int f(int a,int b,int c)
{
    std::cout<<a<<' '<<b<<' '<<c<<'\n';
    return 0;
}

Но после сбоя в компиляции. (функция шаблона) (см. живое демо здесь.)

#include <iostream>

template <typename T> 
void f(T a,int b,int c=3);
template <typename T> 
void f(T a,int b=9,int c); // compiler error why???

int main()
{
    f(3);
    f(3,6);
    f(3,6,9);
    return 0;
}

template <typename T> 
void f(T a,int b,int c)
{
    std::cout<<a<<' '<<b<<' '<<c<<'\n';
} 
4b9b3361

Ответ 1

Это историческое ограничение, которое было добавлено довольно рано в процессе стандартизации (оно было на С++ 98, но не в ARM).

Я не помню точной причины (и мой коллега, который почти наверняка был там, когда было принято решение). Однако у меня есть предположение...

В то время все-единственные компиляторы создавали шаблоны, переигрывая токены через синтаксический анализ. Некоторые едва разбирались в шаблонах. Рассмотрим:

template<class T> struct S {
  T f(T);  // (1)
};
template<class T> T S<T>::f(T p = 42) { return p; }  // (2)
S<int> s;  // Causes the "real" parsing of (1), but not (2).
int r = s.f();  // (3)

При разрешении вызова (3) более старые компиляторы поэтому часто имели доступ только к инстанцированному объявлению (1), тогда как (2) все еще не разбирались (только токены-буферизированы). В результате такие компиляторы не знали о добавленном аргументе по умолчанию в (3).

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

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

Ответ 2

Потому что это принципиально невозможно.

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

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

К сожалению, для функций шаблона это становится... более сложным. Рассмотрим:

template<typename T> void f(T t);
template<typename U> std::enable_if_t<std::is_same<U, int>::value> f(U u = U());

Вы можете видеть, что они, вероятно, объявляют одну и ту же функцию, если T - int. В противном случае они этого не делают. Есть более проблематичные взаимодействия с аргументами шаблона по умолчанию и схожими вещами, но длинный рассказ короткий, это было бы неразрешимым для шаблонов.