C ++ правила об эквивалентности / переопределении шаблонов? - PullRequest
1 голос
/ 04 октября 2019

Следующая единица перевода C ++ неверна из-за трех строк:

template <int x, int y> struct S {}; // [1]
template <int w, int z> struct S {}; // [2] <-- ill-formed
template <int x> struct S<x,5+2> {}; // [3]
template <int w> struct S<w,3+4> {}; // [4] <-- ill-formed
template <> struct S<6+1,4+3> {}; // [5] 
template <> struct S<2+5,8-1> {}; // [6] <-- ill-formed

Проблема в том, что [2] является переопределением [1], [4] переопределением [3]и [6] переопределение [5].

Какой конкретный язык в стандарте C ++ используется для того, чтобы сделать его некорректным?

Где оно указывается, когда два определения шаблонаопределить одну и ту же специализацию шаблона (и когда они являются двумя разными специализациями шаблона)?

Где говорится, что это запрещено (переопределить одну и ту же специализацию)?

Ответы [ 2 ]

1 голос
/ 04 октября 2019
template <int x, int y> struct S {}; // [1]
template <int w, int z> struct S {}; // [2] <-- ill-formed

Это запрещено стандартным , общим C ++ материалом.

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

Правило одного определения гласит:

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

В операторе [1] создается шаблонная сущность. Эта сущность обозначается именем S.

В операторе [2] создается шаблонная сущность. Эта сущность обозначается именем S.

Эти два имени являются одинаковыми . Если имя обозначает объект (как ранее установлено), то то же имя в соответствующей области действия обозначает тот же объект. Таким образом, оба заявления объявляют одну и ту же сущность.

Что запрещено ODR.


template <> struct S<6+1,4+3> {}; // [5] 
template <> struct S<2+5,8-1> {}; // [6] <-- ill-formed

Грамматически говоря, S<6+1,4+3> - это simple-шаблон-ID . Теперь можно подумать, что это превращает это в определение частичной специализации шаблона класса . Но это не так, потому что параметр шаблона пуст, что делает это явной специализацией . Несмотря на это, S<6+1,4+3> все еще является названием этого определения. И явные определения специализации по-прежнему являются определениями.

Итак, вопрос в том, является ли S<6+1,4+3> тем же именем, что и S<2+5,8-1>?

Вот выдержка из правил о том, как мы говорим, что то же имя :

они идентификаторы шаблона , которые ссылаются на один и тот же класс, функцию или переменную ([temp.type])

A simple-template-id является грамматически подмножеством template-id . Итак, эти два simple-template-id s "относятся к одному и тому же классу, функции или переменной"?

Это определяется сложным набором правил ,В частности, обратите внимание:

их соответствующие нетипизированные аргументы шаблона целочисленного или перечислимого типа имеют одинаковые значения

6+1 и 2+5 - это одно и то же значение. 4+3 и 8-1 - это одно и то же значение. Следовательно, эти два simple-template-id s имеют одинаковое имя и, следовательно, одну и ту же сущность. И поскольку эти два определения определяют одну и ту же сущность, которая нарушает ODR, как указывалось ранее.


Частичная специализация шаблонов - это другое дело.

template <int x> struct S<x,5+2> {}; // [3]
template <int w> struct S<w,3+4> {}; // [4] <-- ill-formed

Предыдущая логика, окружающая simple-template-id работает ... в точку. Частичные специализации используют simple-template-id s для своих имен, поэтому вводимые ими сущности используют одни и те же правила для проверки эквивалентности.

Однако эти правила на самом деле не говорят, что здесь происходит,x и w не являются значениями;они являются параметрами шаблона. Их действительная ценность может быть известна только тогда, когда выбраны их специализации. А другие правил эквивалентности на самом деле не говорят о том, что происходит, когда аргумент шаблона является параметром шаблона.

Так что технически , может показаться, что это не тактакое же определение. Однако ...

В соответствии с правилами , как выбирается специализация шаблона , операторы [3] и [4] вводят частичные специализации, которые полностью неоднозначно . Видите, правила выбора между специализациями включают в себя сложный набор вещей, включая преобразование ваших частичных специализаций в серию объявлений функций и использование правил перегрузки шаблонных функций для их сортировки. ,Поэтому я не цитирую это.

То, как все сводится к вашим двум типам, заключается в том, что любой S<value>, который вы пытаетесь создать, будет неоднозначным. Нет никакого способа обнаружить [3] из [4], поэтому любая попытка использовать любой из них будет плохо сформирована.

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

Возможно, что-то в спецификации было пропущено, что фактическичто имена одинаковы, поскольку они , очевидно, одно и то же имя. Но я не нашел его.

1 голос
/ 04 октября 2019

Я собираюсь выйти здесь на конечность и сказать, что здесь применимо « правило одного определения » (выделите их и мое):

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

Обратите внимание, что шаблоныподпадают под это правило, а не только переменные.


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

Это можно увидеть , выполнив сначала 3, а не 1:

template <int x> struct S {}; // [3] <-- S takes one parameter now
template <int x, int y> struct S {}; // [1] <-- this is a redefinition, because S only takes one parameter.

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


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

template <typename... T>
struct Foo;

template <typename T1>
struct Foo<T1> {};

template <typename T1, typename T2>
struct Foo<T1,T2> {};

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

...