Dependent names
Внутри определения шаблона (как шаблона класса , так и шаблона функции ) значение некоторых конструкций может отличаться от одной инстанциации к другой. В частности, типы и выражения могут зависеть от типов параметров шаблона типа и значений параметров шаблона-константы.
template<typename T> struct X : B<T> // "B<T>" зависит от T { typename T::A* pa; // "T::A" зависит от T // (см. ниже значение использования "typename") void f(B<T>* pb) { static int i = B<T>::i; // "B<T>::i" зависит от T pb->j++; // "pb->j" зависит от T } };
Имя lookup и связывание различаются для зависимых и независимых имён.
Правила связывания
Независимые имена ищутся и связываются в точке определения шаблона. Эта связь сохраняется, даже если в точке инстанцирования шаблона существует более подходящее соответствие:
Если значение не зависящего имени изменяется между контекстом определения и точкой инстанциации специализации шаблона, программа является некорректной, диагностика не требуется. Это возможно в следующих ситуациях:
- тип, используемый в независимом имени, является неполным в точке определения, но становится полным в точке инстанцирования
|
(начиная с C++17) |
- инстанцирование использует аргумент по умолчанию или аргумент шаблона по умолчанию, который не был определен в точке определения
- константное выражение constant expression в точке инстанцирования использует значение константного объекта целочисленного типа или неограниченного перечисления , значение constexpr объекта, значение ссылки или определение constexpr функции (since C++11) , и этот объект /ссылка/функция (since C++11) не был определен в точке определения
- шаблон использует не зависимую специализацию шаблона класса или специализацию шаблона переменной (since C++14) в точке инстанцирования, и этот используемый шаблон либо инстанцирован из частичной специализации, которая не была определена в точке определения, либо указывает на явную специализацию, которая не была объявлена в точке определения
Привязка зависимых имен откладывается до момента выполнения поиска.
Правила поиска
Поиск lookup зависимого имени, используемого в шаблоне, откладывается до тех пор, пока не станут известны аргументы шаблона, после чего
- поиск без ADL рассматривает объявления функций с внешней линковкой, которые видны из контекста определения шаблона
- ADL рассматривает объявления функций с внешней линковкой, которые видны либо из контекста определения шаблона, либо из контекста инстанцирования шаблона
(другими словами, добавление нового объявления функции после определения шаблона не делает её видимой, за исключением ADL).
Цель этого правила — помочь предотвратить нарушения ODR для инстанцирования шаблонов:
// внешняя библиотека namespace E { template<typename T> void writeObject(const T& t) { std::cout << "Value = " << t << '\n'; } } // единица трансляции 1: // Программист 1 хочет разрешить E::writeObject работать с vector<int> namespace P1 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ' '; return os; } void doSomething() { std::vector<int> v; E::writeObject(v); // Ошибка: не найдет P1::operator<< } } // единица трансляции 2: // Программист 2 хочет разрешить E::writeObject работать с vector<int> namespace P2 { std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) { for (int n : v) os << n << ':'; return os << "[]"; } void doSomethingElse() { std::vector<int> v; E::writeObject(v); // Ошибка: не найдет P2::operator<< } }
В приведенном выше примере, если бы не-ADL поиск для
operator<<
был разрешен из контекста инстанцирования, инстанцирование
E
::
writeObject
<
vector
<
int
>>
имело бы два различных определения: одно с использованием
P1
::
operator
<<
и другое с использованием
P2
::
operator
<<
. Такое нарушение ODR может быть не обнаружено компоновщиком, что приведет к использованию одного или другого оператора в обоих случаях.
Чтобы ADL рассматривал пользовательское пространство имён, либо std::vector должен быть заменён пользовательским классом, либо его тип элемента должен быть пользовательским классом:
namespace P1 { // если C - класс, определенный в пространстве имен P1 std::ostream& operator<<(std::ostream& os, const std::vector<C>& v) { for (C n : v) os << n; return os; } void doSomething() { std::vector<C> v; E::writeObject(v); // OK: инстанцирует writeObject(std::vector<P1::C>) // который находит P1::operator<< через ADL } }
Примечание: это правило делает непрактичным перегрузку операторов для типов стандартной библиотеки:
#include <iostream> #include <iterator> #include <utility> #include <vector> // Плохая идея: оператор в глобальном пространстве имен, но его аргументы находятся в std:: std::ostream& operator<<(std::ostream& os, std::pair<int, double> p) { return os << p.first << ',' << p.second; } int main() { typedef std::pair<int, double> elem_t; std::vector<elem_t> v(10); std::cout << v[0] << '\n'; // OK, обычный поиск находит ::operator<< std::copy(v.begin(), v.end(), std::ostream_iterator<elem_t>(std::cout, " ")); // Ошибка: как обычный поиск с точки определения // std::ostream_iterator, так и ADL будут рассматривать только пространство имен std, // и найдут множество перегрузок std::operator<<, поэтому поиск будет выполнен. // Затем разрешение перегрузки не сможет найти operator<< для elem_t // в наборе, найденном при поиске. }
Примечание: ограниченный поиск (но не привязка) зависимых имен также происходит во время определения шаблона, по мере необходимости, чтобы отличать их от независимых имен, а также чтобы определить, являются ли они членами текущей инстанциации или членами неизвестной специализации. Информация, полученная в результате этого поиска, может использоваться для обнаружения ошибок, см. ниже.
Зависимые типы
Следующие типы являются зависимыми типами :
- параметр шаблона
- член неизвестной специализации (см. ниже)
- вложенный класс/перечисление, являющийся зависимым членом неизвестной специализации (см. ниже)
- cv-квалифицированная версия зависимого типа
- составной тип, сконструированный из зависимого типа
- массив, тип элементов которого является зависимым или граница которого (если есть) является зависимой по значению
|
(since C++11) |
- тип функции, спецификация исключений которой зависит от значения
- template-id , где либо
-
- имя шаблона является параметром шаблона, или
- любой из аргументов шаблона является тип-зависимым, или значение-зависимым , или является pack expansion (since C++11) (даже если template-id используется без своего списка аргументов, как injected-class-name )
Результат применения decltype к выражению, зависящему от типа, является уникальным зависимым типом. Два таких результата ссылаются на один и тот же тип только если их выражения эквивалентны . |
(since C++11) |
Спецификатор индексации пакета, примененный к типово-зависимому константному выражению, является уникальным зависимым типом. Два таких спецификатора индексации пакета ссылаются на один и тот же тип только в том случае, если их константные выражения эквивалентны. В противном случае два таких спецификатора индексации пакета ссылаются на один и тот же тип только в том случае, если их индексы имеют одинаковое значение. |
(since C++26) |
Примечание: typedef-член текущей инстанциации является зависимым только тогда, когда тип, на который он ссылается, является зависимым.
Выражения, зависящие от типа
Следующие выражения являются type-dependent :
- выражение, любой подвыражение которого является зависимым от типа выражением
- this , если класс является зависимым типом.
- выражение-идентификатор, которое не является concept-id и (начиная с C++20)
-
- содержит идентификатор, для которого поиск имени находит хотя бы одно зависимое объявление
- содержит зависимый template-id
|
(since C++11) |
-
- содержит имя conversion function для зависимого типа
- содержит спецификатор вложенного имени или qualified-id , который является членом неизвестной специализации
- указывает на зависимый член текущей инстанциации, который является статическим элементом данных типа "массив неизвестной границы"
|
(since C++14) |
|
(since C++17) |
|
(since C++26) |
- любое выражение приведения к зависимому типу
- new выражение создающее объект зависимого типа
- выражение доступа к члену, ссылающееся на член текущей инстанциации, тип которого является зависимым
- выражение доступа к члену, ссылающееся на член неизвестной специализации
| (since C++17) |
|
(since C++26) |
Следующие выражения никогда не являются зависимыми от типа, поскольку типы этих выражений не могут быть:
| (начиная с C++11) |
`, которые согласно инструкциям не подлежат переводу. Поэтому перевод не требуется, и исходный HTML-код остается без изменений.
| (начиная с C++20) |
Выражения, зависящие от значения
Следующие выражения являются value-dependent :
- выражение, используемое в контексте, где требуется константное выражение , и любой из его подвыражений является value-dependent
- выражение-идентификатор , удовлетворяющее любому из следующих условий:
|
(since C++20) |
-
- Это зависит от типа.
- Это имя параметра шаблона, являющегося константой.
- Это имя статического члена данных, который является зависимым членом текущей инстанциации и не инициализирован.
- Это имя статической функции-члена, которая является зависимым членом текущей инстанциации.
- Это константа с целочисленным или перечисляемым (до C++11) литеральным (начиная с C++11) типом, инициализированная из выражения, зависящего от значения.
- следующие выражения, где операнд является типозависимым выражением:
| (начиная с C++11) |
- следующие выражения, где операнд является зависимым type-id:
- следующие выражения, в которых целевой тип является зависимым или операнд представляет собой типозависимое выражение:
- function-style cast выражение, в котором целевой тип является зависимым или зависимое по значению выражение заключено в круглые скобки или фигурные скобки (начиная с C++11)
|
(начиная с C++11) |
| (начиная с C++17) |
- выражение взятия адреса, где аргументом является квалифицированный идентификатор , который именует зависимый член текущей инстанциации
- выражение взятия адреса, где аргументом является любое выражение, которое, вычисляемое как ядро константного выражения , ссылается на шаблонную сущность , являющуюся объектом со статической или поточной (thread) продолжительностью хранения (начиная с C++11) или функцией-членом.
Зависимые имена
|
Этот раздел не завершён
Причина: отсутствует вводная часть из [temp.dep] (выражение-идентификатор, за которым следует список в круглых скобках... |
|
Этот раздел не завершён
Причина: необходимо переформулировать для большей ясности (или по крайней мере сделать менее пугающим), и заодно применить CWG issue 591 |
Текущая инстанциация
В определении шаблона класса (включая его функции-члены и вложенные классы) некоторые имена могут быть определены как ссылающиеся на текущую инстанциацию . Это позволяет обнаруживать определённые ошибки в точке определения, а не инстанциации, и устраняет требование использования typename и template дизъюнкторов для зависимых имён, см. ниже.
Только следующие имена могут ссылаться на текущую инстанциацию:
-
в определении шаблона класса, вложенного класса шаблона класса, члена шаблона класса или члена вложенного класса шаблона класса:
- внедренное-имя-класса шаблона класса или вложенного класса
-
в определении основного шаблона класса или члена основного шаблона класса:
- имя шаблона класса, за которым следует список аргументов шаблона (или эквивалентная специализация псевдонима шаблона) для основного шаблона, где каждый аргумент эквивалентен (определено ниже) своему соответствующему параметру.
-
в определении вложенного класса шаблона класса:
- имя вложенного класса, используемое как член текущей инстанциации
-
в определении частичной специализации шаблона класса или члена частичной специализации шаблона класса:
- имя шаблона класса, за которым следует список аргументов шаблона для частичной специализации, где каждый аргумент эквивалентен своему соответствующему параметру
- в определении шаблонной функции :
Аргумент шаблона эквивалентен параметру шаблона, если
- для параметра типа , аргумент шаблона обозначает тот же тип, что и параметр шаблона.
- для константного параметра , аргумент шаблона является идентификатором , который именует переменную, эквивалентную параметру шаблона. Переменная эквивалентна параметру шаблона, если
-
- он имеет тот же тип, что и параметр шаблона (игнорируя cv-квалификаторы) и
- его инициализатор состоит из единственного идентификатора, который именует параметр шаблона или, рекурсивно, такую переменную.
template<class T> class A { A* p1; // A является текущей инстанциацией A<T>* p2; // A<T> является текущей инстанциацией ::A<T>* p4; // ::A<T> является текущей инстанциацией A<T*> p3; // A<T*> не является текущей инстанциацией class B { B* p1; // B является текущей инстанциацией A<T>::B* p2; // A<T>::B является текущей инстанциацией typename A<T*>::B* p3; // A<T*>::B не является текущей инстанциацией }; }; template<class T> class A<T*> { A<T*>* p1; // A<T*> является текущей инстанциацией A<T>* p2; // A<T> не является текущей инстанциацией }; template<int I> struct B { static const int my_I = I; static const int my_I2 = I + 0; static const int my_I3 = my_I; static const long my_I4 = I; static const int my_I5 = (I); B<my_I>* b1; // B<my_I> является текущей инстанциацией: // my_I имеет тот же тип, что и I, // и инициализируется только I B<my_I2>* b2; // B<my_I2> не является текущей инстанциацией: // I + 0 не является одиночным идентификатором B<my_I3>* b3; // B<my_I3> является текущей инстанциацией: // my_I3 имеет тот же тип, что и I, // и инициализируется только my_I (что эквивалентно I) B<my_I4>* b4; // B<my_I4> не является текущей инстанциацией: // тип my_I4 (long) не совпадает с типом I (int) B<my_I5>* b5; // B<my_I5> не является текущей инстанциацией: // (I) не является одиночным идентификатором };
Обратите внимание, что базовый класс может быть текущей инстанциацией, если вложенный класс наследует от объемлющего шаблона класса. Базовые классы, которые являются зависимыми типами, но не являются текущей инстанциацией, называются зависимыми базовыми классами :
template<class T> struct A { typedef int M; struct B { typedef void M; struct C; }; }; template<class T> struct A<T>::B::C : A<T> { M m; // OK, A<T>::M };
Имя классифицируется как член текущей инстанциации, если оно
- неквалифицированное имя, которое найдено с помощью unqualified lookup в текущей инстанциации или в её независимой базе.
-
qualified name
, если квалификатор (имя слева от
::) указывает на текущую инстанциацию и поиск находит имя в текущей инстанциации или в её независимой базе - имя, используемое в выражении доступа к члену класса ( y в x. y или xp - > y ), где выражение объекта ( x или * xp ) является текущей инстанциацией и поиск находит имя в текущей инстанциации или в её независимой базе
template<class T> class A { static const int i = 5; int n1[i]; // i ссылается на член текущей инстанциации int n2[A::i]; // A::i ссылается на член текущей инстанциации int n3[A<T>::i]; // A<T>::i ссылается на член текущей инстанциации int f(); }; template<class T> int A<T>::f() { return i; // i ссылается на член текущей инстанциации }
Члены текущей инстанциации могут быть как зависимыми, так и независимыми.
Если поиск члена текущей инстанциации дает различный результат между точкой инстанциации и точкой определения, поиск считается неоднозначным. Однако следует отметить, что при использовании имени члена оно не автоматически преобразуется в выражение доступа к члену класса, только явные выражения доступа к членам указывают на члены текущей инстанциации:
struct A { int m; }; struct B { int m; }; template<typename T> struct C : A, T { int f() { return this->m; } // находит A::m в контексте определения шаблона int g() { return m; } // находит A::m в контексте определения шаблона }; template int C<B>::f(); // ошибка: находит как A::m, так и B::m template int C<B>::g(); // OK: преобразование в синтаксис доступа к членам класса // не происходит в контексте определения шаблона
Неизвестные специализации
В определении шаблона некоторые имена выводятся как принадлежащие к неизвестной специализации , в частности,
-
квалифицированное имя, если любое имя, появляющееся слева от
::, является зависимым типом, не являющимся членом текущей инстанциации - квалифицированное имя, квалификатором которого является текущая инстанциация, и имя не найдено в текущей инстанциации или любых её не зависимых базовых классах, и существует зависимый базовый класс
- имя члена в выражении доступа к члену класса ( y в x. y или xp - > y ), если тип выражения объекта ( x или * xp ) является зависимым типом и не является текущей инстанциацией
- имя члена в выражении доступа к члену класса ( y в x. y или xp - > y ), если тип выражения объекта ( x или * xp ) является текущей инстанциацией, и имя не найдено в текущей инстанциации или любых её не зависимых базовых классах, и существует зависимый базовый класс
template<typename T> struct Base {}; template<typename T> struct Derived : Base<T> { void f() { // Derived<T> ссылается на текущую инстанциацию // в текущей инстанциации нет "unknown_type" // но существует зависимая база (Base<T>) // Следовательно, "unknown_type" является членом неизвестной специализации typename Derived<T>::unknown_type z; } }; template<> struct Base<int> // эта специализация предоставляет его { typedef int unknown_type; };
Эта классификация позволяет обнаруживать следующие ошибки в точке определения шаблона (а не инстанцирования):
- Если какое-либо определение шаблона содержит квалифицированное имя , в котором квалификатор ссылается на текущую инстанциацию, а имя не является членом текущей инстанциации или членом неизвестной специализации, программа является некорректной (диагностика не требуется), даже если шаблон никогда не инстанцируется.
template<class T> class A { typedef int type; void f() { A<T>::type i; // OK: «type» является членом текущей инстанциации typename A<T>::other j; // Ошибка: // «other» не является членом текущей инстанциации // и не является членом неизвестной специализации, // так как A<T> (которое ссылается на текущую инстанциацию) // не имеет зависимых базовых классов, где мог бы скрываться «other». } };
- Если любое определение шаблона содержит выражение доступа к члену, в котором объектное выражение является текущей инстанциацией, но имя не является ни членом текущей инстанциации, ни членом неизвестной специализации, программа является некорректной, даже если шаблон никогда не инстанцируется.
Члены неизвестной специализации всегда являются зависимыми и ищутся и связываются в точке инстанцирования, как и все зависимые имена (см. выше)
Дизамбигуатор typename для зависимых имен
В объявлении или определении шаблона, включая псевдоним шаблона, имя, которое не является членом текущей конкретизации и зависит от параметра шаблона, не считается типом, если не используется ключевое слово typename или если оно уже не было установлено как имя типа, например, с помощью объявления typedef или путем использования для именования базового класса.
#include <iostream> #include <vector> int p = 1; template<typename T> void foo(const std::vector<T> &v) { // std::vector<T>::const_iterator является зависимым именем, typename std::vector<T>::const_iterator it = v.begin(); // без "typename" следующее выражение парсится как умножение // зависимого от типа члена данных "const_iterator" // и некоторой переменной "p". Поскольку глобальная переменная "p" видна // в этой точке, это определение шаблона компилируется. std::vector<T>::const_iterator* p; typedef typename std::vector<T>::const_iterator iter_t; iter_t * p2; // "iter_t" является зависимым именем, но известно, что это имя типа } template<typename T> struct S { typedef int value_t; // член текущей инстанциации void f() { S<T>::value_t n{}; // S<T> является зависимым, но "typename" не требуется std::cout << n << '\n'; } }; int main() { std::vector<int> v; foo(v); // инстанцирование шаблона завершается неудачей: нет переменной-члена // с именем "const_iterator" в типе std::vector<int> S<int>().f(); }
Ключевое слово typename может использоваться таким образом только перед квалифицированными именами (например, T :: x ), но имена не обязательно должны быть зависимыми.
Обычный qualified name lookup используется для идентификатора с префиксом typename . В отличие от случая с elaborated type specifier , правила поиска не меняются несмотря на квалификатор:
struct A // A имеет вложенную переменную X и вложенный тип struct X { struct X {}; int X; }; struct B { struct X {}; // B имеет вложенный тип struct X }; template<class T> void f(T t) { typename T::X x; } void foo() { A a; B b; f(b); // OK: инстанцирует f<B>, T::X ссылается на B::X f(a); // ошибка: невозможно инстанцировать f<A>: // потому что поиск по квалифицированному имени для A::X находит член-данные }
Ключевое слово typename может использоваться даже вне шаблонов.
#include <vector> int main() { // Оба варианта корректны (после разрешения CWG 382) typedef typename std::vector<int>::const_iterator iter_t; typename std::vector<int> v; }
|
В некоторых контекстах могут корректно появляться только имена типов. В этих контекстах предполагается, что зависимое квалифицированное имя обозначает тип, и typename не требуется:
|
(начиная с C++20) |
Дизамбигуатор template для зависимых имен
Аналогично, в определении шаблона зависимое имя, не являющееся членом текущей инстанциации, не считается именем шаблона, если не используется уточняющее ключевое слово template или если оно не было ранее установлено как имя шаблона:
template<typename T> struct S { template<typename U> void foo() {} }; template<typename T> void bar() { S<T> s; s.foo<T>(); // ошибка: < интерпретируется как оператор меньше s.template foo<T>(); // OK }
Ключевое слово template может использоваться таким образом только после операторов :: (разрешения области видимости), - > (доступ к члену через указатель) и . (доступ к члену), следующие примеры являются корректными:
- T :: template foo < X > ( ) ;
- s. template foo < X > ( ) ;
- this - > template foo < X > ( ) ;
- typename T :: template iterator < int > :: value_type v ;
Как и в случае с typename , префикс template разрешён, даже если имя не является зависимым или использование не находится в области шаблона.
Даже если имя слева от
::
ссылается на namespace, использование template disambiguator разрешено:
template<typename> struct S {}; ::template S<void> q; // разрешено, но не обязательно
|
Из-за специальных правил для неполного поиска имени для шаблонных имен в выражениях доступа к членам, когда не зависящее шаблонное имя появляется в выражении доступа к членам (после - > или после . ), уточнение не требуется, если шаблон класса или псевдонима (начиная с C++11) с тем же именем найден обычным поиском в контексте выражения. Однако, если шаблон, найденный поиском в контексте выражения, отличается от найденного в контексте класса, программа некорректна (до C++11) template<int> struct A { int value; }; template<class T> void f(T t) { t.A<0>::value; // Ordinary lookup of A finds a class template. // A<0>::value names member of class A<0> // t.A < 0; // Error: “<” is treated as the start of template argument list } |
(до C++23) |
Ключевые слова
Отчёты о дефектах
Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 206 | C++98 |
не было указано, в какой момент применяются семантические ограничения,
когда тип, используемый в независимом имени, является неполным в точке определения шаблона, но становится полным в точке инстанцирования |
программа является некорректной
и диагностика не требуется в данном случае |
| CWG 224 | C++98 |
определение зависимых типов основывалось
на форме имени, а не на поиске |
определение переработано |
| CWG 382 | C++98 | дизамбигуатор typename был разрешен только в области видимости шаблона |
также разрешен вне
шаблонов |
| CWG 468 | C++98 | дизамбигуатор template был разрешен только в области видимости шаблона |
также разрешен вне
шаблонов |
| CWG 502 | C++98 | не было указано, являются ли вложенные перечисления зависимыми | зависимы как вложенные классы |
| CWG 1047 | C++98 | typeid выражения никогда не были зависимыми по значению |
зависимы по значению, если
операнд зависим по типу |
| CWG 1160 | C++98 |
не было указано, ссылается ли имя на текущую инстанциацию,
когда идентификатор шаблона, соответствующий основному шаблону или частичной специализации, появляется в определении члена шаблона |
указано |
| CWG 1413 | C++98 |
неинициализированный статический член данных, статическая функция-член и адрес
члена шаблона класса не были перечислены как зависимые по значению |
перечислены |
| CWG 1471 | C++98 |
вложенный тип независимого базового класса
текущей инстанциации был зависимым |
не является зависимым |
| CWG 1850 | C++98 |
список случаев, когда значение может изменяться между
контекстом определения и точкой инстанциации, был неполным |
сделан полным |
| CWG 1929 | C++98 |
не было ясно, может ли дизамбигуатор
template
следовать за
::
, где имя слева ссылается на пространство имен
|
разрешено |
| CWG 2066 | C++98 | this никогда не был зависимым по значению |
может быть
зависимым по значению |
| CWG 2100 | C++98 |
адрес статического члена данных шаблона класса
не был перечислен как зависимый по значению |
перечислен |
| CWG 2109 | C++98 | выражения-идентификаторы, зависимые по типу, могут не быть зависимыми по значению |
они всегда
зависимы по значению |
| CWG 2276 | C++98 |
тип функции, чья спецификация исключений
зависима по значению, не был зависимым типом |
является |
| CWG 2307 | C++98 |
заключенный в скобки постоянный параметр шаблона, используемый как
аргумент шаблона, был эквивалентен этому параметру шаблона |
больше не эквивалентен |
| CWG 2457 | C++11 |
тип функции с пакетом параметров функции
не был зависимым типом |
является |
| CWG 2785 | C++20 | requires выражения могут быть зависимыми по типу |
они никогда не
зависимы по типу |
| CWG 2905 | C++11 |
выражение
noexcept
было зависимым по значению только
если его операнд зависим по значению |
оно зависимо по значению
если его операнд включает параметр шаблона |
| CWG 2936 | C++98 |
имена локальных классов шаблонных
функций не были частью текущей инстанциации |
являются |