Хотя правила в C ++ 03 о том, когда вам нужны typename
и template
, в значительной степени разумны, есть один неприятный недостаток его формулировки
template
struct A {
typedef int result_type;
void f() {
// error, "this" is dependent, "template" keyword needed
this->g();
// OK
g();
// error, "A" is dependent, "typename" keyword needed
A::result_type n1;
// OK
result_type n2;
}
template
void g();
};
. Как можно видеть, нам нужно ключевое слово disamiguation, даже если компилятор мог бы понять, что A::result_type
может быть только int
(и, следовательно, является типом) и this->g
может быть только членом шаблона g
, объявленного позже (даже если A
явно специализирован где-то, что не повлияет на код внутри этого шаблона, поэтому его значение не может быть затронуто более поздней специализацией A
!).
Чтобы улучшить ситуацию, на C ++ 11 язык отслеживает, когда тип относится к охватывающему шаблону. Чтобы знать это, тип должен быть сформирован с использованием определенной формы имени, которая является ее собственным именем (в приведенных выше, A
, A
, ::A
). Известно, что тип, на который ссылается такое имя, является текущим экземпляром . Может быть несколько типов, которые являются текущим экземпляром, если тип, из которого формируется имя, является членом / вложенным классом (тогда A::NestedClass
и A
являются текущими экземплярами).
Исходя из этого понятия, язык говорит, что CurrentInstantiation::Foo
, Foo
и CurrentInstantiationTyped->Foo
(такие как A *a = this; a->Foo
) являются участником текущего экземпляра , если они являются членами класса, который является текущим экземпляром или одним из его независящих базовых классов (просто выполнив поиск имени сразу).
Теперь ключевые слова typename
и template
больше не требуются, если определитель является членом текущего экземпляра. Здесь следует помнить, что A
- все еще имя, зависящее от типа (ведь T
также зависит от типа). Но A
, как известно, является типом - компилятор будет «волшебным образом» смотреть на такого рода зависимые типы, чтобы понять это.
struct B {
typedef int result_type;
};
template
struct C { }; // could be specialized!
template
struct D : B, C {
void f() {
// OK, member of current instantiation!
// A::result_type is not dependent: int
D::result_type r1;
// error, not a member of the current instantiation
D::questionable_type r2;
// OK for now - relying on C to provide it
// But not a member of the current instantiation
typename D::questionable_type r3;
}
};
Это впечатляет, но можем ли мы сделать лучше? Язык даже идет дальше, и требует , чтобы реализация снова смотрела D::result_type
при создании экземпляра D::f
(даже если она нашла свое значение уже во время определения). Когда результат поиска отличается или приводит к двусмысленности, программа плохо сформирована и должна быть дана диагностика. Представьте, что произойдет, если мы определим C
, как это
template<>
struct C {
typedef bool result_type;
typedef int questionable_type;
};
. Компилятор должен улавливать ошибку при создании экземпляра D
. Таким образом, вы получаете лучший из двух миров: «Отложенный» поиск защищает вас, если вы можете столкнуться с проблемами с зависимыми базовыми классами, а также «немедленный» поиск, который освобождает вас от typename
и template
.
В коде D
имя typename D::questionable_type
не является членом текущего экземпляра. Вместо этого язык обозначает его как члена неизвестной специализации . В частности, это всегда происходит, когда вы делаете DependentTypeName::Foo
или DependentTypedName->Foo
, и либо зависимый тип , ни текущий экземпляр (в этом случае компилятор может отказаться и сказать «мы будем Посмотрите позже, что Foo
), или - текущий экземпляр, и имя не было найдено в нем или его независящих базовых классах, и есть также зависимые базовые классы.
Представьте, что произойдет, если у нас была функция-член h
в пределах указанного выше шаблона класса A
void h() {
typename A::questionable_type x;
}
. В C ++ 03 язык позволил поймать эту ошибку, потому что никогда не может быть действительный способ создания экземпляра A
(любой аргумент, который вы даете T
). В C ++ 11 теперь у пользователя есть дополнительная проверка, чтобы дать больше причин для компиляторов реализовать это правило. Поскольку A
не имеет зависимого базовые классы и A
объявляют ни одного члена questionable_type
, имя A
не является ни членом текущего экземпляра , ни членом неизвестной специализации. в этом случае что этот код не может быть достоверно скомпилирован во время создания экземпляра, поэтому язык запрещает имя, в котором спецификатор является текущим экземпляром, не является ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение все еще не является
Вы можете попробовать это знание на этом ответе и посмотреть, действительно ли эти определения имеют смысл для вас реальный пример (в этом ответе они несколько менее детализированы).
Правила C ++ 11 делают неправильный код действующего C ++ 03 (который не был предназначен комитетом C ++, но, вероятно, не будет исправлен)
struct B { void f(); };
struct A : virtual B { void f(); };
template
struct C : virtual B, T {
void g() { this->f(); }
};
int main() {
C c; c.g();
}
Этот действительный код C ++ 03 связывает this->f
с A::f
во время создания экземпляра, и все в порядке. Однако C ++ 11 привязывает его к B::f
и требует двойной проверки при создании экземпляра, проверяя, соответствует ли поиск. Однако при создании экземпляра C::g
применяется правило Dominance Rule , и поиск будет искать A::f
.