Почему шаблонная функция члена данных зависит от имени только при квалификации с помощью "this"?

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-> делает этот код двусмысленным для компилятора?

p.s. Первоначально bar был элементом данных шаблона базового класса, который нуждался в двусмысленности с this->, но я упростил пример для цели этого вопроса.

Ответы

Ответ 1

this->bar.baz<int>(); // error

Утверждение выше, в определении template<typename T> Foo<T>::Foo(), является корректным и должно быть принято, если включен режим С++ 11 или С++ 1y. Но это было технически плохо сформировано в соответствии с С++ 03.

Оба стандарта согласны с тем, что this является зависимым от типа выражением:

С++ 03 14.6.2.1/1; N3690 14.6.2.1/8:

Тип зависит, если он

  • параметр шаблона,

  • ...

  • [simple-] template-id, в котором либо имя шаблона является параметром шаблона, либо любой из аргументов шаблона является зависимым типом или выражением, зависящим от типа или зависящим от значения,

[T является зависимым типом, и поэтому Foo<T>.]

С++ 03/N3690 14.6.2.2/2:

this зависит от типа, зависит от типа класса входящей функции-члена.

[Поскольку Foo<T> является зависимым типом, выражение this в определении его члена зависит от типа.]

Оба стандарта начинаются с 14.6.2.2:

За исключением случаев, описанных ниже, выражение зависит от типа, если любое подвыражение зависит от типа.

С++ 03 имеет только три простые категории выражений с более точными описаниями:

  • Первичные выражения (this и просмотренные имена)

  • Выражения, которые определяют их собственный тип (например, приведения и новые выражения)

  • Выражения с постоянным типом (например, литералы и sizeof).

Первая категория определена в С++ 03 14.6.2.2/3:

Идентификатор id зависит от типа, если он содержит:

  • идентификатор, объявленный с зависимым типом,

  • идентификатор шаблона, который зависит,

  • идентификатор функции преобразования, который задает зависимый тип,

  • спецификатор вложенного имени, который содержит имя класса, которое называет зависимый тип.

Таким образом, одиночное выражение bar не зависит: оно является идентификатором и id-выражением, но ни одно из вышеприведенных условий не применяется.

Но this->bar не является id-выражением или в любом из других исключений С++ 03, поэтому мы должны следовать правилу подвыражения. Поскольку подвыражение this зависит от типа, содержащее выражение this->bar также зависит от типа.

Но на самом деле, как вы заметили, тип this->bar может быть известен при анализе определения шаблона без создания экземпляров любых аргументов шаблона. Он объявляется как член первичного шаблона, поэтому имя должно привязываться к объявлению участника. Специализация шаблона может сделать Foo<T>::bar необъявленным или объявленным по-другому, но в этом случае основной шаблон не будет использоваться вообще, и текущее определение Foo() игнорируется для этой специализации. Именно поэтому С++ 11 определил концепцию "текущего экземпляра" и использовал ее для дальнейшего исключения из-за заразности зависимых от типа выражений.

N3690 14.6.2.1/1:

Имя относится к текущему экземпляру, если оно

  • в определении шаблона класса, вложенного класса шаблона класса, члена шаблона класса или члена вложенного класса шаблона класса, имя класса-инъекции класса шаблон или вложенный класс

  • в определении шаблона первичного класса или члена шаблона первичного класса, имя шаблона класса, за которым следует список аргументов шаблона основного шаблона (как описано ниже), заключенный в <> ( или эквивалентная спецификация шаблона шаблона),

  • ...

[Первая пуля говорит, что Foo является текущим экземпляром. Второй говорит, что Foo<T> является текущим экземпляром. В этом примере оба названия одного типа.]

14.6.2.1/4:

Имя является членом текущего экземпляра, если оно

  • Неквалифицированное имя, которое при просмотре относится к хотя бы одному члену класса, который является текущим экземпляром или его независящим базовым классом.

  • Квалифицированный идентификатор, в котором...

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

[Первая пуля говорит, что только bar является членом текущего экземпляра. Третий пул говорит, что this->bar является членом текущего экземпляра.]

Наконец, С++ 11 добавляет четвертую категорию правил для зависимых от типа выражений, для доступа членов. 14.6.2.2/5:

Выражение доступа к члену класса зависит от типа, если выражение относится к члену текущего экземпляра, и тип ссылочного элемента зависит, или выражение доступа к члену класса относится к члену неизвестной специализации.

this->bar ссылается на элемент текущего экземпляра, но тип bar ссылочного элемента не зависит. Таким образом, теперь this->bar не зависит от типа, а имя baz в this->bar.baz просматривается во время определения шаблона как не зависящее от имени. Ключевое слово template не требуется до baz.

Ответ 2

Краткое описание

Это как раз то, как текущие правила С++ 11: this->bar.baz<int>() вводит зависимое имя не в текущем контексте контекста, который требует значения с ключевым словом template, хотя очень сложно найти фактический пример конкурирующего анализа, который изменяет семантику выражения this->bar.baz<int>().

Неоднозначность интерпретации из угловых скобок

Во-первых: почему вообще существует потребность в template?

Когда компилятор С++ встречает выражение f<g>(0), он может интерпретировать это как "вызывать шаблон функции f для аргумента шаблона g и аргумент функции 0 и оценивать результат", или это может означать "сравните (f<g)>(0) для имен f и g и константы 0." Без дополнительной информации он не может принять это решение. Это неудачное следствие выбора угловых скобок для аргументов шаблона.

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

Поиск двухфазного имени

Поскольку имя, зависящее от шаблона, может изменить его значение (например, через специализации), когда шаблон создается для конкретного типа, поиск имен зависимых имен выполняется в два этапа (цитата из Шаблоны С++ Полное руководство):

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

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

Почему this-> делает ваш код разным

Использование this-> внутри шаблона класса вводит зависимое имя и вызывает поиск двухфазного имени. В этом случае вступает в действие правило, указанное в @40two. Дело в том, что ADL на 2-й фазе может вносить новые имена из явных специализаций, которые переопределяют смысл ваших bar и baz, и это могло бы, по-видимому, изменить значение this->bar.baz<int>(0) на сравнение, а не на шаблон функции вызов.

Предположим, что для аргументов типа non-type, таких как this->bar.another_baz<0>(), это будет более вероятно, чем для параметра шаблона типа. В этом связанном Q & A возникла аналогичная дискуссия, можно ли найти синтаксическую действительную форму, которая изменяет значение this->f<int>() vs this->template f<int>(0), без четкого заключения.

Обратите внимание, что С++ 11 уже расслабляет правило для значения template по сравнению с С++ 98. В соответствии с текущими правилами this->f<int>() для template<class> f() внутри Foo не требуется template, потому что он находится в так называемом текущем экземпляре. См. этот ответ из канонического Q & A для получения более подробной информации

Ответ 3

В соответствии со стандартом § 14.2/4 Имена специализированных шаблонов [temp.names]

Когда имя специализации шаблона элемента появляется после . или -> в постфиксном выражении или после специфицированного вложенного имени в квалификационном идентификаторе, а выражение объекта постфиксного выражения является типом -зависимый или вложенный имя-спецификатор в идентификаторе qualid-id относится к зависимому типу, но имя не является членом текущего экземпляра (14.6.2.1), имя шаблона члена должно иметь префикс с ключевым словом template.

Edit:

Кроме того, согласно стандарту § 14.6.2.2/2 Типозависимые выражения [temp.dep.expr]:

this зависит от типа, если тип класса входящей функции-члена зависит (14.6.2.1).

Таким образом, для вызова bar.baz<int>() через this вам нужно префикс с ключевым словом template:

this->bar.template baz<int>();

LIVE DEMO

[ Причина:] Компилятор нуждается в этом "повторном" использовании ключевого слова template, потому что он не может решить, является ли токен < operator< или начало списка аргументов шаблона.