Ключевые слова template и typename в C++
Ключевые слова template и typename в C++ используются в двух контекстах. Один из них хорошо известен программистам: определение шаблонов. Другое использование менее очевидно: указание того, что выражение относится к шаблонной функции или типу. Это часто приводит к ошибкам у программистов, использующих библиотеку Eigen, которые сложно интерпретировать, например, "ожидается выражение" или "нет совпадения для оператора <".
Использование ключевых слов template и typename для определения шаблонов
Ключевые слова template и typename обычно используются для определения шаблонов. Эта тема не рассматривается на данной странице, поскольку предполагается, что читатель знаком с этим (в противном случае обратитесь к книге по C++). Следующий пример продемонстрирует использование ключевого слова template.
template <typename T> bool isPositive(T x) { return x > 0; }
Мы могли бы точно так же написать template <class T>; ключевые слова typename и class имеют одинаковое значение в этом контексте.
Пример, демонстрирующий второе использование ключевого слова template
Давайте проиллюстрируем второе использование ключевого слова template на примере. Предположим, нам нужно написать функцию, которая копирует все элементы верхней треугольной части матрицы в другую матрицу, оставив нижнюю треугольную часть без изменений. Простой реализацией будет:
| Пример: | Вывод: |
|---|---|
#include <Eigen/Dense> #include <iostream> using namespace Eigen; void copyUpperTriangularPart(MatrixXf& dst, const MatrixXf& src) { dst.triangularView<Upper>() = src.triangularView<Upper>(); } int main() { MatrixXf m1 = MatrixXf::Ones(4,4); MatrixXf m2 = MatrixXf::Random(4,4); std::cout << "m2 before copy:" << std::endl; std::cout << m2 << std::endl << std::endl; copyUpperTriangularPart(m2, m1); std::cout << "m2 after copy:" << std::endl; std::cout << m2 << std::endl << std::endl; } |
m2 before copy:
0.68 0.823 -0.444 -0.27
-0.211 -0.605 0.108 0.0268
0.566 -0.33 -0.0452 0.904
0.597 0.536 0.258 0.832
m2 after copy:
1 1 1 1
-0.211 1 1 1
0.566 -0.33 1 1
0.597 0.536 0.258 1
|
Это работает, но не очень гибко. Во-первых, оно работает только с динамическими матрицами одинарной точности с плавающей запятой; функция copyUpperTriangularPart() не принимает статические матрицы или матрицы с двойной точностью. Во-вторых, если вы используете выражение, такое как mat.topLeftCorner(3,3) в качестве параметра src, то оно копируется во временную переменную типа MatrixXf; эту копию можно избежать.
Как объяснено в Написание функций, принимающих типы Eigen в качестве параметров, обе проблемы можно решить, сделав copyUpperTriangularPart() принимающим любой объект типа MatrixBase. Это приводит к следующему коду:
| Пример: | Вывод: |
|---|---|
#include <Eigen/Dense> #include <iostream> using namespace Eigen; template <typename Derived1, typename Derived2> void copyUpperTriangularPart(MatrixBase<Derived1>& dst, const MatrixBase<Derived2>& src) { /* Note the 'template' keywords in the following line! */ dst.template triangularView<Upper>() = src.template triangularView<Upper>(); } int main() { MatrixXi m1 = MatrixXi::Ones(5,5); MatrixXi m2 = MatrixXi::Random(4,4); std::cout << "m2 before copy:" << std::endl; std::cout << m2 << std::endl << std::endl; copyUpperTriangularPart(m2, m1.topLeftCorner(4,4)); std::cout << "m2 after copy:" << std::endl; std::cout << m2 << std::endl << std::endl; } |
m2 before copy: 7 9 -5 -3 -2 -6 1 0 6 -3 0 9 6 6 3 9 m2 after copy: 1 1 1 1 -2 1 1 1 6 -3 1 1 6 6 3 1 |
Одна строка в теле функции copyUpperTriangularPart() демонстрирует второе, менее очевидное использование ключевого слова template в C++. Хотя это может показаться странным, ключевые слова template необходимы согласно стандарту. Без них компилятор может отклонить код с сообщением об ошибке типа "нет совпадения для оператора <".
Объяснение
Причина необходимости ключевого слова template в последнем примере связана с правилами компиляции шаблонов в C++. Компилятор должен проверить код на правильность синтаксиса в момент определения шаблона, не зная фактического значения аргументов шаблона (Derived1 и Derived2 в примере). Это означает, что компилятор не может знать, что dst.triangularView является шаблонным членом и что символ < является частью разделителя параметра шаблона. Другой вариант - dst.triangularView является членом переменной, а символ < относится к функции operator<(). Фактически, согласно стандарту, компилятор должен выбрать второй вариант. Если dst.triangularView является шаблонным членом (как в нашем случае), программист должен явно указать это с помощью ключевого слова template и написать dst.template triangularView.
Точные правила довольно сложные, но игнорируя некоторые тонкости, их можно обобщить следующим образом:
- Зависимое имя - это имя, которое зависит (прямо или косвенно) от параметра шаблона. В примере
dst- это зависимое имя, поскольку оно имеет типMatrixBase<Derived1>, который зависит от параметра шаблонаDerived1. - Если код содержит конструкции
xxx.yyyилиxxx->yyyиxxxявляется зависимым именем, аyyyотносится к шаблонному члену, то ключевое словоtemplateдолжно использоваться передyyy, что приводит кxxx.template yyyилиxxx->template yyy. - Если код содержит конструкцию
xxx::yyyиxxxявляется зависимым именем, аyyyотносится к шаблонному typedef, то ключевое словоtypenameдолжно использоваться перед всей конструкцией, что приводит кtypename xxx::yyy.
Пример, где необходимо ключевое слово typename, можно найти в Обработке разреженных матриц для итерации по ненулевым элементам типа разреженной матрицы:
SparseMatrixType mat(rows,cols); for (int k=0; k<mat.outerSize(); ++k) for (SparseMatrixType::InnerIterator it(mat,k); it; ++it) { /* ... */ }
Если SparseMatrixType зависит от параметра шаблона, то необходимо ключевое слово typename:
template <typename T> void iterateOverSparseMatrix(const SparseMatrix<T>& mat; { for (int k=0; k<m1.outerSize(); ++k) for (typename SparseMatrix<T>::InnerIterator it(mat,k); it; ++it) { /* ... */ } }
Ресурсы для дальнейшего изучения
Для получения дополнительной информации и более полного объяснения данной темы читатель может обратиться к следующим источникам:
- Книга "C++ Template Metaprogramming" Дэвида Абрахамса и Алексея Гуртовоя содержит очень хорошее объяснение в Приложении B ("Ключевые слова typename и template"), которое послужило основой для данной страницы.
- http://pages.cs.wisc.edu/~driscoll/typename.html
- http://www.parashift.com/c++-faq-lite/templates.html#faq-35.18
- http://www.comeaucomputing.com/techtalk/templates/#templateprefix
- http://www.comeaucomputing.com/techtalk/templates/#typename
© Eigen.
Licensed under the MPL2 License.
https://eigen.tuxfamily.org/dox/TopicTemplateKeyword.html