struct Bar {
template<typename>
void baz() {
}
};
template<typename>
struct Foo {
Bar bar;
Foo() {
bar.baz<int>();
}
};
int main() {
return 0;
}
Этот код компилируется нормально (в GCC 4.7), но если я префикс вызова bar.baz<int>()
с this->
, baz
становится зависимым именем, которое нуждается в устранении неоднозначности template
,
bar.baz<int>(); // OK
this->bar.baz<int>(); // error
this->bar.template baz<int>(); // OK
конечно this->bar
может относиться только к Bar bar
чей член baz
это явно шаблон? Почему добавление this->
сделать этот код неоднозначным для компилятора?
постскриптум Первоначально, bar
был членом данных шаблона базового класса, который требовал устранения неоднозначности с this->
, но я упростил пример для целей этого вопроса.
this->bar.baz<int>(); // error
Заявление выше, в рамках определения template<typename T> Foo<T>::Foo()
, правильно сформирован и должен приниматься, если включен режим C ++ 11 или режим C ++ 1y. Но это было технически плохо сформировано согласно C ++ 03.
Оба стандарта согласны с тем, что this
является зависимым от типа выражением:
С ++ 03 14.6.2.1/1; N3690 14.6.2.1/8:
[Тип зависит, если он
параметр шаблона,
…
- [просто-]Шаблон-идентификатор в котором либо имя шаблона является параметром шаблона, либо любой из аргументов шаблона является зависимым типом или выражением, которое зависит от типа или значения,
T
является зависимым типом, и поэтому Foo<T>
.]
С ++ 03 / N3690 14.6.2.2/2:
[Поскольку
this
зависит от типа, если тип класса вмещающей функции-члена является зависимым.
Foo<T>
это зависимый тип, выражение this
в его определении члена зависит от типа.]
Оба стандарта начинаются с 14.6.2.2:
За исключением случаев, описанных ниже, выражение зависит от типа, если любое подвыражение зависит от типа.
C ++ 03 имеет только три простые категории выражений с более точными описаниями:
Основные выражения (this
и подыскивал имена)
Выражения, которые определяют их собственный тип (например, приведение типов и новые выражения)
Выражения с постоянным типом (например, литералы и sizeof
).
Первая категория определена в C ++ 03 14.6.2.2/3:
ID-выражение зависит от типа, если оно содержит:
идентификатор который был объявлен с зависимым типом,
Шаблон-идентификатор это зависит,
преобразование-функция-идентификатор который определяет зависимый тип,
вложенное имя спецификатор который содержит Имя_класса который называет зависимый тип.
Так что одинокое выражение bar
не зависит: это идентификатор и ID-выражение, но ничего из вышеперечисленного не применимо.
Но this->bar
это не ID-выражение, или в любом другом исключении C ++ 03, поэтому мы должны следовать правилу подвыражения. С подвыражением this
зависит от типа, содержащее выражение this->bar
также зависит от типа.
Но на самом деле, как вы заметили, тип this->bar
может быть известно при разборе определения шаблона, без создания каких-либо аргументов шаблона. Он объявлен как член основного шаблона, поэтому имя должно быть связано с этим объявлением члена. Специализация шаблона может сделать Foo<T>::bar
необъявленный или объявленный другим способом, но в этом случае основной шаблон не будет использоваться вообще, а текущее определение Foo()
игнорируется для этой специализации. Вот почему C ++ 11 определил понятие «текущая реализация» и использовал его для дальнейшего исключения из заразности зависимых от типа выражений.
N3690 14.6.2.1/1:
[Первая пуля говоритНазвание относится к текущая реализация если это
в определении шаблона класса, вложенный класс шаблона класса, член шаблона класса или член вложенного класса шаблона класса, имя введенного класса шаблона класса или вложенного класса
в определении шаблона первичного класса или члена шаблона первичного класса, имя шаблона класса, за которым следует список аргументов шаблона первичного шаблона (как описано ниже), заключенный в
<>
(или эквивалентная специализация псевдонима шаблона),…
Foo
текущая реализация Второй говорит Foo<T>
текущая реализация В этом примере оба имени имеют одинаковый тип.]
14.6.2.1/4:
[Первая пуля говоритИмя это член текущей инстанции если это
Неквалифицированное имя, которое при поиске относится, по крайней мере, к одному члену класса, который является текущим экземпляром, или его независимому базовому классу.
Квалифицированный-идентификатор в котором …
ID-выражение обозначение члена в выражении доступа к члену класса, для которого тип выражения объекта является текущей реализацией, и ID-выражение, при поиске относится, по крайней мере, к одному члену класса, который является текущим экземпляром, или его независимому базовому классу.
bar
один является членом текущего экземпляра. Третья пуля говорит this->bar
является членом текущего экземпляра.]
Наконец, C ++ 11 добавляет четвертую категорию правил для зависимых от типа выражений для доступа к элементу. 14.6.2.2/5:
Выражение доступа к члену класса зависит от типа, если выражение относится к члену текущего экземпляра и тип ссылочного члена является зависимым, или выражение доступа к члену класса относится к члену неизвестной специализации.
this->bar
ссылается на член текущего экземпляра, но тип Bar
от указанного члена не зависит. А сейчас this->bar
не зависит от типа, а имя baz
в this->bar.baz
ищется во время определения шаблона как независимое имя. template
Ключевое слово не нужно раньше baz
,
Вот как текущие правила C ++ 11: this->bar.baz<int>()
вводит зависимое имя не в текущем контексте реализации, который требует устранения неоднозначности с template
ключевое слово, хотя очень трудно найти реальный пример конкурирующего анализа, который изменяет семантику выражения this->bar.baz<int>()
,
Во-первых: зачем вообще нужна template
?
Когда компилятор C ++ встречает выражение f<g>(0)
, он может интерпретировать это как «вызов шаблона функции f
для аргумента шаблона g
и аргумент функции 0
и оценить результат «или это может означать» сделать сравнение (f<g)>(0)
для имен f
а также g
и постоянный 0
. «Без дополнительной информации он не может принять это решение. Это является печальным следствием выбора угловых скобок для аргументов шаблона.
Во многих (большинстве) случаях компилятор делает иметь достаточно контекста, чтобы решить, анализируется ли выражение шаблона или сравнение. Тем не менее, когда так называемый зависимое имя (по сути, встречается имя, которое явно или неявно зависит от параметра шаблона текущей области), вступает в игру другая языковая тонкость.
Поскольку имя, зависящее от шаблона, может изменить свое значение (например, посредством специализаций), когда создается экземпляр шаблона для конкретного типа, поиск имени зависимых имен выполняется в два этапа (цитата из Шаблоны C ++ Полное руководство):
На первом этапе ищутся независимые имена, а
шаблон анализируется с использованием как обычных правил поиска, так и, если
применимы правила для аргументно-зависимого поиска (ADL).
Неквалифицированные зависимые имена (которые являются зависимыми, потому что они выглядят как имя функции в вызове функции с зависимой
аргументы) также смотрятся таким образом, но результат поиска
не считается завершенным, пока не будет выполнен дополнительный поиск
когда шаблон создан.На втором этапе, который происходит, когда создаются шаблоны
в точке, называемой точкой инстанции (POI), зависимой
поиск квалифицированных имен (с заменой параметров шаблона)
с аргументами шаблона для этого конкретного экземпляра), и
дополнительный ADL выполняется для неквалифицированных зависимых имен.
this->
делает ваш код другимС помощью this->
внутри шаблона класса вводится зависимое имя и запускается двухфазный поиск имени. В этом случае вступает в силу правило, цитируемое @ 40two. Дело в том, что ADL на 2-м этапе может вводить новые имена из явных специализаций, которые переопределяют значение вашего bar
а также baz
и это может предположительно изменить значение this->bar.baz<int>(0)
для сравнения, а не вызова шаблона функции.
Конечно, для не типовых аргументов шаблона, таких как this->bar.another_baz<0>()
это было бы более вероятно, чем для параметра шаблона типа. В это связано Q& аналогичное обсуждение возникло, можно ли найти синтаксическую действительную форму, которая изменяет значение this->f<int>()
против this->template f<int>(0)
без четкого заключения.
Обратите внимание, что C ++ 11 уже ослабляет правило для template
неоднозначность по сравнению с C ++ 98. По действующим правилам this->f<int>()
для template<class> f()
внутри Foo
не требует template
потому что это в так называемом текущая реализация. Увидеть этот ответ от канонический Q& по этой теме для более подробной информации
В соответствии со стандартом § 14.2 / 4 Названия шаблонных специализаций [temp.names]
Когда имя специалиста шаблона члена появляется после
.
или же->
в выражении post-x или после вложенного-name-specer-а в квалифицированном id, а объектное выражение post-x-expression зависит от типа, или nested-name-specer в квалифицированном-id ссылается на зависимый тип, но имя не является членом текущего экземпляра (14.6.2.1), перед именем шаблона элемента должно стоять ключевое словоtemplate
,
Редактировать:
Также по стандарту § 14.6.2.2/2 Типозависимые выражения [temp.dep.expr]:
this
зависит от типа, если тип класса функции-члена является зависимым (14.6.2.1).
Таким образом, чтобы позвонить bar.baz<int>()
с помощью this
вам нужно префикс по ключевому слову template
:
this->bar.template baz<int>();
[Причина:]
Компилятору нужно это «избыточное» использование template
ключевое слово, потому что не может решить, является ли токен <
является operator<
или начало списка аргументов шаблона.