Зависимые имена
Внутри определения шаблона (как шаблона класса, так и шаблона функции) значение некоторых конструкций может отличаться от одной инстанциации к другой. В частности, типы и выражения могут зависеть от типов параметров шаблонов типов и значений параметров шаблонов нетипового типа.
template<typename T>
struct X : B<T> // "B<T>" is dependent on T
{
typename T::A* pa; // "T::A" is dependent on T
// (see below for the meaning of this use of "typename")
void f(B<T>* pb)
{
static int i = B<T>::i; // "B<T>::i" is dependent on T
pb->j++; // "pb->j" is dependent on T
}
};Поиск имени и привязка отличаются для зависимых имён и независимых имён.
Правила привязки
Независимые имена ищутся и связываются в точке определения шаблона. Эта привязка сохраняется даже если в точке инстанциации существует лучшее соответствие:
#include <iostream>
void g(double) { std::cout << "g(double)\n"; }
template<class T>
struct S
{
void f() const
{
g(1); // "g" is a non-dependent name, bound now
}
};
void g(int) { std::cout << "g(int)\n"; }
int main()
{
g(1); // calls g(int)
S<int> s;
s.f(); // calls g(double)
}Если значение независимого имени изменяется между контекстом определения и точкой инстанциации специализации шаблона, программа является некорректной, диагностика не требуется. Это возможно в следующих ситуациях:
- тип, используемый в независимом имени, является неполным в момент определения, но полным в момент инстанциации
| (с C++17) |
- инстанциация использует аргумент по умолчанию или аргумент шаблона по умолчанию, который не был определен в точке определения
- константное выражение в точке инстанциации использует значение объекта const целочисленного или неименованного перечислимого типа, значение объекта constexpr, значение ссылки или определение constexpr-функции(с C++11), и что объект/ссылка/функция(с C++11) не был определен в точке определения
- шаблон использует специализацию не зависящего шаблона класса или специализацию шаблона переменной(с C++14) в момент инстанциации, и этот используемый шаблон либо инстанцируется из частичной специализации, которая не была определена в точке определения, либо называет явную специализацию, которая не была объявлена в точке определения
Привязка зависимых имён откладывается до тех пор, пока не произойдёт поиск.
Правила поиска
Как обсуждалось в поиске, поиск зависимого имени, используемого в шаблоне, откладывается до тех пор, пока аргументы шаблона не станут известны, в этот момент
- поиск без ADL исследует объявления функций с внешней связью, видимые из контекста определения шаблона
- ADL исследует объявления функций с внешней связью, видимые как из контекста определения шаблона, так и из контекста инстанциации шаблона
(другими словами, добавление нового объявления функции после определения шаблона не делает его видимым, за исключением случая ADL).
Цель этого правила состоит в том, чтобы защититься от нарушений правила ODR для инстанциаций шаблонов:
// an external library
namespace E
{
template<typename T>
void writeObject(const T& t)
{
std::cout << "Value = " << t << '\n';
}
}
// translation unit 1:
// Programmer 1 wants to allow E::writeObject to work with 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); // error: will not find P1::operator<<
}
}
// translation unit 2:
// Programmer 2 wants to allow E::writeObject to work with 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); // error: will not find P2::operator<<
}
}В приведённом выше примере, если поиск без ADL для operator<< был разрешён из контекста инстанциации, инстанциация E::writeObject<vector<int>> имела бы два различных определения: одно, использующее P1::operator<<, и другое, использующее P2::operator<<. Такое нарушение ODR может не быть обнаружено линковщиком, что приводит к использованию того или иного определения в обеих инстанциациях.
Чтобы ADL исследовал пользовательское пространство имён, либо std::vector должно быть заменено пользовательским классом, либо его тип элемента должен быть пользовательским классом:
namespace P1
{
// if C is a class defined in the P1 namespace
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: instantiates writeObject(std::vector<P1::C>)
// which finds P1::operator<< via ADL
}
}Примечание: это правило делает непрактичным перегрузку операторов для типов стандартной библиотеки:
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>
// Bad idea: operator in global namespace, but its arguments are in 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, ordinary lookup finds ::operator<<
std::copy(v.begin(), v.end(),
std::ostream_iterator<elem_t>(std::cout, " "));
// Error: both ordinary lookup from the point of definition of
// std::ostream_iterator and ADL will only consider the std namespace,
// and will find many overloads of std::operator<<, so the lookup will be done.
// Overload resolution will then fail to find operator<< for elem_t
// in the set found by the lookup.
}Примечание: ограниченный поиск (но не привязка) зависимых имён также происходит во время определения шаблона, по мере необходимости, чтобы отличить их от независимых имён, а также определить, являются ли они членами текущей инстанциации или членами неизвестной специализации. Полученная при этом информация может использоваться для обнаружения ошибок, см. ниже.
Зависимые типы
Следующие типы являются зависимыми типами:
- параметр шаблона
- член неизвестной специализации (см. ниже)
- вложенный класс/перечисление, являющийся зависимым членом неизвестной специализации (см. ниже)
- версия типа с квалификаторами cv зависимого типа
- составной тип, построенный из зависимого типа
- тип массива, элемент которого является зависимым или связанное значение является значение-зависимым
| (с C++11) |
- тип функции, спецификация исключений которой зависит от значения
- template-id, где либо
- имя шаблона является параметром шаблона, или
- любой из аргументов шаблона является типом-зависимым или значение-зависимым, или является расширением пакета(с C++11) (даже если template-id используется без списка аргументов, как injected-class-name)
Результат применения | (с C++11) |
Спецификатор индексации пакета, применённый к константному выражению, зависящему от типа, представляет собой уникальный зависимый тип. Два таких спецификатора индексации пакета относятся к одному типу только если их константные выражения равны. В противном случае два таких спецификатора индексации пакета относятся к одному типу только если их индексы имеют одинаковое значение. | (с C++26) |
Примечание: typedef-член текущей инстанциации является зависимым только тогда, когда зависимым является тип, к которому он относится.
Выражения, зависящие от типа
Следующие выражения являются зависимыми от типа:
- выражение, любое подвыражение которого является выражением, зависящим от типа
-
this, если класс является зависимым типом. - выражение id, которое не является concept-id и(с C++20)
- содержит идентификатор, для которого поиск имени находит по крайней мере одно зависимое объявление
- содержит зависимый template-id
| (с C++11) |
- содержит имя функции преобразования к зависимому типу
- содержит квалифицированное имя или qualified-id, являющийся членом неизвестной специализации
- называет зависимый член текущей инстанциации, который является статическим данным членом типа "массив неизвестной длины"
| (с C++14) |
| (с C++17) |
- любое выражение приведения к зависимому типу
- new-выражение, создающее объект зависимого типа
- выражение доступа к члену, которое ссылается на член текущей инстанциации, тип которого зависит
- выражение доступа к члену, которое ссылается на член неизвестной специализации
| (с C++17) |
| (с C++26) |
Примечание: литералы, псевдо-вызовы деструкторов, alignof, noexcept(с C++11), sizeof, typeid, delete и throw-выражения никогда не являются типом-зависимыми, потому что типы этих выражений не могут быть.
Значения-зависимые выражения
- выражение, используемое в контексте, где требуется константное выражение, и любое его подвыражение является значение-зависимым
- id-выражение, которое
| (с C++20) |
- является типом-зависимым
- является именем параметра шаблона нетипа
- называет статическое член-данное, являющееся зависимым членом текущей инстанциации и не инициализированное.
- называет статический член-функцию, являющуюся зависимым членом текущей инстанциации
- является константой с типом целого или перечисления(до C++11)литерала(с C++11), инициализированной из значение-зависимого выражения
-
alignof,noexcept,(с C++11)sizeof,typeid-выражения, где аргумент является типом-зависимым выражением или зависимым type-id - любое выражение приведения к зависимому типу или из значение-зависимого выражения
- адресное выражение, где аргумент — qualified-id, которое называет зависимый член текущей инстанциации
- адресное выражение, где аргумент — любое выражение, которое, вычисленное как ядро константное выражение, ссылается на шаблонное сущность, которая является объектом со статическим или потоковым хранилищем(с C++11) продолжительностью или членом-функцией.
| (с C++17) |
Зависимые имена
Текущая инстанциация
В определении шаблона класса (включая его член-функции и вложенные классы) некоторые имена могут быть выведены для ссылки на текущую инстанциацию. Это позволяет обнаружить определенные ошибки на этапе определения, а не инстанциации, и отменяет требование к typename и template разделителям для зависимых имен, см. ниже.
Только следующие имена могут ссылаться на текущую инстанциацию:
- в определении шаблона класса:
- вложенный класс, член шаблона класса, член вложенного класса, имя встроенного класса шаблона, имя встроенного класса вложенного класса.
- в определении основного шаблона класса или в определении его члена:
- имя шаблона класса, за которым следует список аргументов шаблона (или эквивалентная специализация шаблона алиаса) для основного шаблона, где каждый аргумент эквивалентен (определено ниже) соответствующему параметру.
- в определении вложенного класса или шаблона класса:
- имя вложенного класса, используемого как член текущей инстанциации.
- в определении частичной специализации или члена частичной специализации:
- имя шаблона класса, за которым следует список аргументов шаблона для частичной специализации, где каждый аргумент эквивалентен соответствующему параметру.
Аргумент шаблона эквивалентен параметру шаблона, если
- для параметра шаблона типа аргумент шаблона обозначает тот же тип, что и параметр шаблона.
- для параметра шаблона нетипа аргумент шаблона — идентификатор, который называет переменную, которая эквивалентна параметру шаблона. Переменная эквивалентна параметру шаблона, если
- она имеет тот же тип, что и параметр шаблона (без учёта cv-квалификации) и
- её инициализатор состоит из одного идентификатора, который называет параметр шаблона или, рекурсивно, такую переменную.
template<class T>
class A
{
A* p1; // A is the current instantiation
A<T>* p2; // A<T> is the current instantiation
::A<T>* p4; // ::A<T> is the current instantiation
A<T*> p3; // A<T*> is not the current instantiation
class B
{
B* p1; // B is the current instantiation
A<T>::B* p2; // A<T>::B is the current instantiation
typename A<T*>::B* p3; // A<T*>::B is not the current instantiation
};
};
template<class T>
class A<T*>
{
A<T*>* p1; // A<T*> is the current instantiation
A<T>* p2; // A<T> is not the current instantiation
};
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> is the current instantiation:
// my_I has the same type as I,
// and it is initialized with only I
B<my_I2>* b2; // B<my_I2> is not the current instantiation:
// I + 0 is not a single identifier
B<my_I3>* b3; // B<my_I3> is the current instantiation:
// my_I3 has the same type as I,
// and it is initialized with only my_I (which is equivalent to I)
B<my_I4>* b4; // B<my_I4> is not the current instantiation:
// the type of my_I4 (long) is not the same as the type of I (int)
B<my_I5>* b5; // B<my_I5> is not the current instantiation:
// (I) is not a single identifier
};Обратите внимание, что базовый класс может быть текущей инстанциацией, если вложенный класс наследуется от его окружающего шаблона класса. Базовые классы, которые являются зависимыми типами, но не являются текущей инстанциацией, являются зависимыми базовыми классами:
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
};Имя классифицируется как член текущей инстанциации, если оно
- неквалифицированное имя, которое найдено с помощью неквалифицированного поиска в текущей инстанциации или в её независимом базовом классе.
- квалифицированное имя, если квалификатор (имя слева от
::) называет текущую инстанциацию, и поиск находит имя в текущей инстанциации или в её независимом базовом классе - имя, используемое в выражении доступа к члену класса (
yвx.yилиxp->y), где выражение объекта (xили*xp) является текущей инстанциацией, и поиск находит имя в текущей инстанциации или в её независимом базовом классе
template<class T>
class A
{
static const int i = 5;
int n1[i]; // i refers to a member of the current instantiation
int n2[A::i]; // A::i refers to a member of the current instantiation
int n3[A<T>::i]; // A<T>::i refers to a member of the current instantiation
int f();
};
template<class T>
int A<T>::f()
{
return i; // i refers to a member of the current instantiation
}Члены текущей инстанциации могут быть одновременно зависимыми и независимыми.
Если поиск члена текущей инстанциации дает разные результаты между точкой инстанциации и точкой определения, поиск является неоднозначным. Однако следует отметить, что при использовании имени члена оно не автоматически преобразуется в выражение доступа к члену класса, только явные выражения доступа к членам указывают члены текущей инстанциации:
struct A { int m; };
struct B { int m; };
template<typename T>
struct C : A, T
{
int f() { return this->m; } // finds A::m in the template definition context
int g() { return m; } // finds A::m in the template definition context
};
template int C<B>::f(); // error: finds both A::m and B::m
template int C<B>::g(); // OK: transformation to class member access syntax
// does not occur in the template definition contextНеизвестные специализации
Внутри определения шаблона некоторые имена выводятся как относящиеся к неизвестной специализации, в частности,
- квалифицированное имя, если любое имя, появляющееся слева от
::является зависимым типом, который не является членом текущей инстанциации - квалифицированное имя, квалификатором которого является текущая инстанциация, и имя не найдено в текущей инстанциации или в любом из её независимых базовых классов, и существует зависимый базовый класс
- имя члена в выражении доступа к члену класса (
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> refers to current instantiation
// there is no 'unknown_type' in the current instantiation
// but there is a dependent base (Base<T>)
// Therefore, unknown_type is a member of unknown specialization
typename Derived<T>::unknown_type z;
}
};
template<>
struct Base<int> // this specialization provides it
{
typedef int unknown_type;
};
Эта классификация позволяет обнаружить следующие ошибки на этапе определения шаблона (а не инстанциации):
- Если в любом определении шаблона есть квалифицированное имя, в котором квалификатор ссылается на текущую инстанциацию, а имя не является членом текущей инстанциации и не является членом неизвестной специализации, программа некорректна (нет необходимости в диагностике), даже если шаблон никогда не будет инстанцирован.
template<class T>
class A
{
typedef int type;
void f()
{
A<T>::type i; // OK: 'type' is a member of the current instantiation
typename A<T>::other j; // Error:
// 'other' is not a member of the current instantiation
// and it is not a member of an unknown specialization
// because A<T> (which names the current instantiation),
// has no dependent bases for 'other' to hide in.
}
};- Если в любом определении шаблона есть выражение доступа к члену, где выражение объекта является текущей инстанциацией, но имя не является членом текущей инстанциации и не является членом неизвестной специализации, программа некорректна, даже если шаблон никогда не будет инстанцирован.
Члены неизвестной специализации всегда зависимые и ищутся и связываются на этапе инстанциации, как и все зависимые имена (см. выше)
Ключевое слово 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 is a dependent name,
typename std::vector<T>::const_iterator it = v.begin();
// without 'typename', the following is parsed as multiplication
// of the type-dependent member variable 'const_iterator'
// and some variable 'p'. Since there is a global 'p' visible
// at this point, this template definition compiles.
std::vector<T>::const_iterator* p;
typedef typename std::vector<T>::const_iterator iter_t;
iter_t * p2; // iter_t is a dependent name, but it's known to be a type name
}
template<typename T>
struct S
{
typedef int value_t; // member of current instantiation
void f()
{
S<T>::value_t n{}; // S<T> is dependent, but 'typename' not needed
std::cout << n << '\n';
}
};
int main()
{
std::vector<int> v;
foo(v); // template instantiation fails: there is no member variable
// called 'const_iterator' in the type std::vector<int>
S<int>().f();
}Ключевое слово typename может использоваться только таким образом перед квалифицированными именами (например, T::x), но имена не обязательно должны быть зависимыми.
Обычный поиск квалифицированных имён используется для идентификатора, префикс которого typename. В отличие от расширенного указателя типа, правила поиска не изменяются несмотря на квалификатор:
struct A // A has a nested variable X and a nested type struct X
{
struct X {};
int X;
};
struct B
{
struct X {}; // B has a nested type struct X
};
template<class T>
void f(T t)
{
typename T::X x;
}
void foo()
{
A a;
B b;
f(b); // OK: instantiates f<B>, T::X refers to B::X
f(a); // error: cannot instantiate f<A>:
// because qualified name lookup for A::X finds the data member
}Ключевое слово typename может использоваться даже вне шаблонов.
#include <vector>
int main()
{
// Both OK (after resolving CWG 382)
typedef typename std::vector<int>::const_iterator iter_t;
typename std::vector<int> v;
}| В некоторых контекстах могут появляться только имена типов. В этих контекстах зависимое квалифицированное имя предполагается как имя типа, и
| (с C++20) |
Разрешение неоднозначностей шаблонов для зависимых имён
Аналогично, в определении шаблона зависимое имя, которое не является членом текущей инстанциации, не считается именем шаблона, если не используется ключевое слово разрешения неоднозначностей template или если оно уже установлено как имя шаблона:
template<typename T>
struct S
{
template<typename U>
void foo() {}
};
template<typename T>
void bar()
{
S<T> s;
s.foo<T>(); // error: < parsed as less than operator
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 разрешен, даже если имя не зависимое или использование не находится в области видимости шаблона.
Даже если имя слева от :: относится к пространству имён, разрешение неоднозначностей шаблонов разрешено:
template<typename>
struct S {};
::template S<void> q; // allowed, but unnecessary| Из-за специальных правил для поиска неквалифицированных имён для имён шаблонов в выражениях доступа к членам, когда не зависящее имя шаблона появляется в выражении доступа к члену (после 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 2100 | C++98 | адрес статического члена данных класса-шаблона не был указан как зависящий от значения | указан |
| CWG 2276 | C++98 | тип функции, спецификация исключений которого зависит от значения, не являлся зависимым типом | является |
| CWG 2307 | C++98 | параметр шаблона, не относящийся к типу, в скобках, используемый в качестве аргумента шаблона, был эквивалентен этому параметру шаблона | больше не эквивалентен |
| CWG 2457 | C++11 | тип функции с параметрами пакета функций не был зависимым типом | является |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/dependent_name