Spec-Zone.ru › Eigen3

Ключевые слова 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

Spec-Zone.ru

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