Структура шаблона с аргументом шаблона по умолчанию не создается

Допустим, у меня есть этот код

template<typename T2, typename T = int>
struct X
{
    static double f;
};

template<typename T>
double X<T>::f = 14.0;

Если я попытаюсь скомпилировать clang, я выдам следующую ошибку

спецификатор вложенного имени 'X::' для объявления не относится к классу, шаблону класса или частичной специализации шаблона класса

и для GCC:

ошибка: определение шаблона не-шаблона 'double X::f'

Вопрос в том :

Почему компилятор хочет, чтобы мы специализировали структуру X следующим образом:

template<typename T2>
struct X<T2,int>
{
    static double f;
};

Первое объявление имеет int в качестве аргумента по умолчанию, почему компилятор не выбирает это объявление?

Я искал в стандартный якорь [temp .spec], но это не помогло.

Я задаю этот вопрос после того, как ответил на этот one на SO.

Спасибо за вашу помощь !


person Pumkko    schedule 25.07.2015    source источник


Ответы (1)


«Почему компилятор хочет, чтобы мы так специализировали структуру X» — это не то, о чем говорят сообщения об ошибках. Вам не нужно этого делать, и вам действительно не следует этого делать, если только вам не нужна частичная специализация и статический член, определенный только для этой частичной специализации.

Проблема в том, что template<typename T2, typename T = int> struct X — это шаблон класса, который имеет два параметра шаблона. Тот факт, что второй имеет аргумент шаблона по умолчанию, не меняет того факта, что параметров по-прежнему два.

Итак, вам нужно определить член шаблона класса как принадлежащий шаблону класса с двумя параметрами, например:

template<typename T2, typename T>
double X<T2, T>::f = 14.0;

Соответствующие параграфы стандарта (N4527, действующий проект):

[14.5.1p3]

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

[14.1p9]

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


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

template<typename T, typename U>
double X<T, U>::f = 14.0;

и он по-прежнему будет определять член правильного шаблона класса X. Однако использование одних и тех же имен может облегчить понимание кода при чтении.


При определении частичной специализации перед определением f в исходном примере template<typename T> double X<T>::f = 14.0; становится допустимым определением члена f частичной специализации template<typename T2> struct X<T2,int> и только этого шаблона (частичные специализации сами по себе являются шаблонами). Элемент f основного шаблона template<typename, typename> struct X остается неопределенным.

Соответствующая формулировка содержится в [14.5.5.3p1]:

Список параметров шаблона члена частичной специализации шаблона класса должен соответствовать списку параметров шаблона частичной специализации шаблона класса. Список аргументов шаблона члена частичной специализации шаблона класса должен соответствовать списку аргументов шаблона частичной специализации шаблона класса. Специализация шаблона класса — это отдельный шаблон. Члены частичной специализации шаблона класса не связаны с членами основного шаблона. [...]

person bogdan    schedule 25.07.2015
comment
Я сделал это не только для того, чтобы заглушить ошибку компилятора. Я думал, что OP не хотел явно указывать второй параметр шаблона, который имеет значение по умолчанию в объявлении. Однако я не могу понять, почему значение по умолчанию не используется, когда мы определяем статический член. Другими словами, почему это неактуально? - person Pumkko; 25.07.2015
comment
@Pumkko Подумайте об этом: если предположить, что ваша версия определения f работает, что вы ожидаете, если вы используете X<char, long>::f где-то в своем коде? - person bogdan; 25.07.2015
comment
@bogdan Я думаю, что компоновщик пожалуется. Если бы определение f со значением по умолчанию сработало, мы бы определили только X<T, int>::f, предполагая, что int является типом по умолчанию. Нет определения для X‹char,long› - person Pumkko; 25.07.2015
comment
@ Пумкко Точно. Итак, вы не определили элемент шаблона template<typename, typename> struct X. Вы попытались определить элемент другого шаблона, и этот шаблон сам по себе не определен. - person bogdan; 25.07.2015
comment
Похоже, @dyp читал между строк вашего вопроса лучше, чем я - вы действительно намеревались определить член частичной специализации шаблона класса, не определяя саму частичную специализацию (частичные специализации сами по себе являются шаблонами). В любом случае, все в ответе остается в силе, возможно, мне следует попытаться добавить некоторые разъяснения, связанные с частичной специализацией. - person bogdan; 25.07.2015
comment
@Pumkko Я обновил ответ, добавив в него соответствующие части комментариев выше. - person bogdan; 25.07.2015
comment
возможно, хороший способ изобразить это явление — подчеркнуть, что аргумент по умолчанию не создает дочерний класс с 1 параметром шаблона. Хотя именно так работают функции. Аргумент по умолчанию в функциях генерирует перегрузку родственной функции с меньшей арностью. Исходя из этого поведения, мы ожидаем, что то же самое произойдет и с шаблонами классов. А оказывается, что нет. Такой родственный класс был бы специализацией, и его нужно было бы объявить вручную. Я прав ? - person v.oddou; 26.06.2019
comment
@v.oddou Аргументы функции по умолчанию не создают никаких перегрузок. Если у вас есть void f(int, int = 7);, f(3) — это просто другой способ записи f(3, 7). Он вызывает точно такую ​​же функцию; новые перегрузки не вводятся. Попытка выполнить void (*p)(int) = f; потерпит неудачу (была бы успешной, если бы у вас действительно была перегрузка void f(int);). Первое объявление объявляет только одну функцию с двумя параметрами; void (*p)(int, int) = f; получится. Аргументы шаблона по умолчанию в этом отношении работают примерно так же: немного больше, чем синтаксический сахар, но ненамного. - person bogdan; 26.06.2019
comment
отмеченный ! доступ по указателю — отличный мысленный эксперимент, показывающий, что новый символ не вводится. Благодарю. - person v.oddou; 27.06.2019
comment
@v.oddou Привет! Для полноты картины, я думаю, стоит привести еще один пример, так как это еще один случай, когда может показаться, что задействованы некоторые функции с меньшим количеством параметров: во время разрешения перегрузки. Учитывая void f(int, long = 7);, void f(long, int = 3); и вызов f(3), обе перегрузки являются жизнеспособными кандидатами, и процесс разрешения перегрузки будет рассматривать только те параметры, для которых вызов имеет аргументы как написано, то есть только первый параметр для каждой функции. Первая перегрузка побеждает и вызывается с аргументами (3, 7). - person bogdan; 27.06.2019
comment
Однако, если вы явно запишете второй аргумент в вызове, f(3, 7), оба параметра обеих функций будут учитываться при разрешении перегрузки, и вызов завершится сбоем как неоднозначный. Тем не менее, речь идет только о списках параметров, которые разрешение перегрузки использует для определения наилучшей перегрузки; во всех случаях задействованы только две функции, и никакие другие. - person bogdan; 27.06.2019