Spec-Zone.ru › C++

Зависимые имена

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

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)
}

Если смысл независимого имени меняется между контекстом определения и точкой инстанцирования специализации шаблона, программа некорректна; диагностика не требуется. Такое возможно в следующих ситуациях:

  • тип, используемый в независимом имени, является неполным в точке определения, но полным в точке инстанцирования
  • поиск имени в определении шаблона обнаружил using-объявление, но поиск в соответствующей области видимости при инстанцировании не обнаруживает объявлений, поскольку using-объявление было раскрытием пакета, а соответствующий пакет пуст
(начиная с C++17)
  • при инстанцировании используется аргумент по умолчанию или аргумент шаблона по умолчанию, который не был определён в точке определения
  • константное выражение в точке инстанцирования использует значение константного объекта целочисленного типа или типа перечисления без области видимости, значение объекта 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<<
    }
}

В приведённом выше примере, если бы поиск operator<<, не связанный с ADL, выполнялся в контексте инстанцирования, инстанцирование 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)
  • тип функции, спецификация исключений которой зависит от значения
  • идентификатор шаблона, если выполняется хотя бы одно из условий:
    • имя шаблона является параметром шаблона или
    • любой из аргументов шаблона зависит от типа или значения либо является раскрытием пакета(начиная с C++11) (даже если идентификатор шаблона используется без списка аргументов, как внедрённое имя класса)
  • результат применения decltype к выражению, зависимому от типа
Результат применения decltype к выражению, зависимому от типа, является уникальным зависимым типом. Два таких результата обозначают один и тот же тип, только если их выражения эквивалентны.
(начиная с C++11)
  • спецификатор индексации пакета pack indexing, применённый к константному выражению, зависимому от типа
  • результат применения спецификатора типа splice к зависимому константному выражению
Спецификатор индексации пакета, применённый к константному выражению, зависимому от типа, является уникальным зависимым типом. Два таких спецификатора индексации пакета обозначают один и тот же тип, только если их константные выражения эквивалентны. В противном случае два таких спецификатора индексации пакета обозначают один и тот же тип, только если их индексы имеют одинаковое значение.
(начиная с C++26)

Примечание: член-typedef текущей инстанциации является зависимым, только если зависим тип, на который он ссылается.

Выражения, зависимые от типа

Следующие выражения являются зависимыми от типа :

  • выражение, любое подвыражение которого зависит от типа
  • this, если класс является зависимым типом.
  • выражение-идентификатор, которое не является concept-id и(начиная с C++20) удовлетворяет любому из следующих условий:
    • содержит идентификатор, для которого поиск имени находит хотя бы одно зависимое объявление
    • содержит зависимый идентификатор шаблона
    • содержит имя функции преобразования к зависимому типу
    • содержит вложенный спецификатор имени или квалифицированный идентификатор, являющийся членом неизвестной специализации
    • называет зависимый член текущей инстанциации, являющийся статическим членом-данными типа «массив неизвестной границы»
  • содержит специальный идентификатор __func__ (если охватывающая функция является шаблоном, нетемплейтным членом шаблона класса или обобщённой лямбдой(начиная с C++14))
(начиная с C++11)
  • содержит идентификатор, для которого поиск имени находит одно или несколько объявлений функций-членов текущей инстанциации, объявленных с выводом типа возвращаемого значения
(начиная с C++14)
  • содержит идентификатор, для которого поиск имени находит объявление структурированной привязки, инициализатор которого зависит от типа
  • содержит идентификатор, для которого поиск имени находит нетиповой параметр шаблона, тип которого содержит заполнитель auto
  • содержит идентификатор, для которого поиск имени находит переменную, объявленную с типом, содержащим тип-заполнитель (например, статический член-данные auto), инициализатор которой зависит от типа
(начиная с C++17)
  • содержит идентификатор, для которого поиск имени находит пакет
  • содержит имя, введённое объявлением-элементом оператора раскрытия S, где для указанного имени задан тип, содержащий тип-заполнитель, и выполняется одно из условий:
    • S не является оператором итерационного раскрытия или
    • инициализатор раскрытия S зависит от типа
(начиная с C++26)
  • любое выражение приведения к зависимому типу
  • new-выражение, создающее объект зависимого типа
  • выражение доступа к члену, ссылающееся на член текущей инстанциации, тип которого является зависимым
  • выражение доступа к члену, ссылающееся на член неизвестной специализации
  • свёрточное выражение
(начиная с C++17)
  • выражение индексации пакета, если его выражение-идентификатор зависит от типа
(начиная с C++26)

Следующие выражения никогда не зависят от типа, поскольку типы этих выражений не могут быть зависимыми:

  • литералы
  • вызовы псевдодеструктора
  • sizeof
  • sizeof...
  • alignof
  • noexcept
(начиная с C++11)
  • throw
  • typeid
  • delete
  • requires
(начиная с C++20)
  • оператор отражения ^^
(начиная с C++26)

Выражения, зависимые от значения

Следующие выражения являются зависимыми от значения :

  • выражение, используемое в контексте, где требуется константное выражение, и любое подвыражение которого зависит от значения
  • выражение-идентификатор, удовлетворяющее любому из следующих условий:
    • Оно зависит от типа.
    • Оно является именем нетипового параметра шаблона.
    • Оно называет статический член-данные, являющийся зависимым членом текущей инстанциации и не имеющий инициализатора.
    • Оно называет статическую функцию-член, являющуюся зависимым членом текущей инстанциации.
    • Это константа целочисленного или перечислимого типа(до C++11) литерального типа(начиная с C++11), инициализированная выражением, зависимым от значения.
  • Оно является concept-id, и хотя бы один из его аргументов является зависимым.
(начиная с C++20)
  • Это имя, введённое объявлением-элементом оператора раскрытия.
(начиная с C++26)
  • следующие выражения, если операнд является выражением, зависимым от типа:
    • sizeof
    • typeid
  • alignof
(начиная с C++11)
  • следующие выражения, если операнд является зависимым type-id:
    • sizeof
    • typeid
  • следующие выражения, если целевой тип является зависимым или операнд представляет собой выражение, зависимое от типа:
    • приведение в стиле C
    • static_cast
    • const_cast
    • reinterpret_cast
    • dynamic_cast
  • приведение в функциональном стиле, если целевой тип является зависимым или выражение, зависимое от значения, заключено в круглые или фигурные(начиная с C++11) скобки
  • sizeof...-выражение, если его операнд не является пакетом структурированной привязки(начиная с C++26)
(начиная с C++11)
  • свёрточное выражение
(начиная с C++17)
  • выражение взятия адреса, где аргумент — квалифицированный идентификатор, называющий зависимый член текущей инстанциации
  • выражение взятия адреса, где аргумент — любое выражение, которое при вычислении как базовое константное выражение ссылается на сущность, являющуюся шаблоном, то есть объект со статической длительностью хранения или длительностью хранения в потоке(начиная с C++11), либо функцию-член.

Зависимые пространства имён

Имена пространств имён являются зависимыми, если они введены определениями псевдонима пространства имён, содержащими зависимые спецификаторы splice.

template <std::meta::info R> int fn() {
    namespace Alias = [:R:];  // [:R:] is dependent
    return typename Alias::T{};  // Alias is dependent
}

namespace NS {
    using T = int;
}

int a = fn<^^NS>();
(начиная с C++26)

Зависимые имена

Текущая инстанциация

В определении шаблона класса (включая его функции-члены и вложенные классы) некоторые имена могут быть определены как ссылающиеся на текущую инстанциацию. Это позволяет обнаруживать некоторые ошибки в точке определения, а не инстанцирования, и отменяет необходимость в разделителях 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 data member “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 is 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;
}

Ключевое слово typename также можно использовать в спецификаторе splice, чтобы указать, что он обозначает тип.

(начиная с C++26)

В некоторых контекстах допустимы только имена типов. В таких контекстах предполагается, что зависимое квалифицированное имя или спецификатор splice(начиная с C++26) обозначает тип, поэтому typename не требуется:

  • Квалифицированное имя, используемое как спецификатор объявления в (верхнеуровневой) последовательности decl-specifier-seq в:
    • простом объявлении или определении функции на уровне пространства имён;
    • объявлении члена класса;
    • объявлении параметра в объявлении члена класса (включая объявления дружественных функций), вне аргументов по умолчанию;
    • объявлении параметра в деклараторе функции или шаблона функции с квалифицированным именем, вне аргументов по умолчанию;
    • объявлении параметра в лямбда-выражении, вне аргументов по умолчанию;
    • объявлении параметра в requires-выражении;
    • типе в объявлении нетипового параметра шаблона;
  • Квалифицированное имя, встречающееся в type-id, если наименьший охватывающий его type-id является:
    • типом в new-выражении, тип которого не заключён в скобки;
    • type-id в объявлении псевдонима;
    • конечным типом возвращаемого значения;
    • аргументом по умолчанию параметра шаблона-типа или
    • type-id в static_cast, dynamic_cast, const_cast или reinterpret_cast.
(начиная с 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>();          // 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

Из-за особых правил поиска неквалифицированных имен для имен шаблонов в выражениях доступа к членам, когда независящее имя шаблона встречается в выражении доступа к члену (после -> или после .), различитель не требуется, если обычный поиск в контексте выражения находит шаблон класса или псевдонима(начиная с 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)

Ключевое слово template также можно использовать в спецификаторе splice, чтобы указать, что он обозначает шаблон.

(начиная с C++26)

Ключевые слова

template, typename

Отчеты об исправлениях дефектов

Следующие отчеты об исправлениях дефектов, изменяющих поведение, были ретроактивно применены к ранее опубликованным стандартам 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 имена локальных классов шаблонных
функций не относились к текущей инстанциации
относятся

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/dependent_name

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API