[Eigen中文文档] C++中的template和typename关键字

简介: 在C++中,template和typename关键字有两种用途。其中一个在程序员中相当有知名度:用于定义模板。另一个用法则更为隐晦:用于指定一个表达式是引用模板函数还是类型。这经常困扰使用Eigen库的程序员,通常会导致编译器难以理解的错误信息,比如 expected expression 或 no match for operator<>。

文档总目录

英文原文(The template and typename keywords in C++)

在C++中,templatetypename关键字有两种用途。其中一个在程序员中相当有知名度:用于定义模板。另一个用法则更为隐晦:用于指定一个表达式是引用模板函数还是类型。这经常困扰使用Eigen库的程序员,通常会导致编译器难以理解的错误信息,比如 expected expressionno match for operator<>

使用 template 和 typename 关键字定义模板

templatetypename关键字通常用于定义模板。本页面不涉及这个主题,我们假设读者已经了解了这一点(否则请参考C++书籍)。以下示例应该说明了template关键字的用法。

template <typename T>
bool isPositive(T x)
{
   
    return x > 0;
}

我们也可以编写 template <class T>; 关键字 typenameclass 在此上下文中具有相同的含义。

显示 template 关键字的第二次使用的示例

让我们通过一个例子来说明template关键字的第二种用法。假设我们想编写一个函数,将矩阵的上三角部分的所有元素复制到另一个矩阵中,同时保持下三角不变。一个简单的实现如下:

#include <Eigen/Dense>
#include <iostream>

using Eigen::MatrixXf;

void copyUpperTriangularPart(MatrixXf& dst, const MatrixXf& src)
{
   
    dst.triangularView<Eigen::Upper>() = src.triangularView<Eigen::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 类型的临时变量中;这个复制是可以避免的。

正如编写以特征类型作为参数的函数中所解释的,这两个问题都可以通过使 copyUpperTrianglePart() 接受任何 MatrixBase 类型的对象来解决。这导致以下代码:

#include <Eigen/Dense>
#include <iostream>

template <typename Derived1, typename Derived2>
void copyUpperTriangularPart(Eigen::MatrixBase<Derived1>& dst, const Eigen::MatrixBase<Derived2>& src)
{
   
  /* Note the 'template' keywords in the following line! */
  dst.template triangularView<Eigen::Upper>() = src.template triangularView<Eigen::Upper>();
}

int main()
{
   
  Eigen::MatrixXi m1 = Eigen::MatrixXi::Ones(5,5);
  Eigen::MatrixXi m2 = Eigen::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() 函数体中的一行代码展示了C++中更为隐晦的第二种使用方式。尽管它可能看起来很奇怪,但是根据标准,使用template关键字是必要的。如果不使用它,编译器可能会拒绝代码并显示类似于 no match for operator< 的错误信息。

解释

在上个例子中,需要使用template关键字的原因与C++中模板的编译规则有关。编译器必须在定义模板的位置检查代码的语法正确性,而不知道模板实参(Derived1Derived2在本例中)的实际值。这意味着编译器不知道dst.triangularView是一个成员模板,并且后面的 < 符号是模板参数的分隔符的一部分。另一个可能性是dst.triangularView是一个成员变量,< 符号引用 operator<() 函数。实际上,根据标准,编译器应该选择第二种可能性。如果dst.triangularView是一个成员模板(如我们的情况),程序员应该使用template关键字显式地指定,并写成dst.template triangularView

确切的规则相当复杂,但忽略一些微妙之处,我们可以将其总结如下:

  • 从属名称是(直接或间接)依赖于模板参数的名称。在示例中,dst 是从属名称,因为它的类型为 MatrixBase<Derived1>,该类型取决于模板参数 Derived1
  • 如果代码包含 xxx.yyyxxx->yyy 构造之一,并且 xxx 是从属名称,并且 yyy 引用成员模板,则必须在 yyy 之前使用 template 关键字,从而导致 xxx.template yyyxxx- > template yyy
  • 如果代码包含构造 xxx::yyy 并且 xxx 是从属名称,并且 yyy 引用成员 typedef,则必须在整个构造之前使用 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)
        {
   
            /* ... */
        }
}

进一步阅读的资源

有关本主题的更多信息和更全面的解释,读者可以查阅以下资源:

相关文章
|
21天前
|
存储 安全 编译器
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
27 0
|
29天前
|
算法 C++ 容器
C++中模板函数以及类模板的示例(template)
C++中模板函数以及类模板的示例(template)
|
1天前
|
C语言 C++
【C++入门】关键字、命名空间以及输入输出
【C++入门】关键字、命名空间以及输入输出
|
21天前
|
C++
|
22天前
|
存储 编译器 Linux
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)
【C++】C++入门第二课(函数重载 | 引用 | 内联函数 | auto关键字 | 指针空值nullptr)
|
22天前
|
编译器 C语言 C++
【C++】C++入门第一课(c++关键字 | 命名空间 | c++输入输出 | 缺省参数)
【C++】C++入门第一课(c++关键字 | 命名空间 | c++输入输出 | 缺省参数)
|
22天前
|
存储 程序员 编译器
C++注释、变量、常量、关键字、标识符、输入输出
C++注释、变量、常量、关键字、标识符、输入输出
|
24天前
|
编译器 C语言 C++
【C++的奇迹之旅(二)】C++关键字&&命名空间使用的三种方式&&C++输入&输出&&命名空间std的使用惯例
【C++的奇迹之旅(二)】C++关键字&&命名空间使用的三种方式&&C++输入&输出&&命名空间std的使用惯例
|
2月前
|
存储 监控 编译器
【C++】static关键字及其修饰的静态成员变量/函数详解
【C++】static关键字及其修饰的静态成员变量/函数详解
37 3
|
2月前
|
安全 程序员 编译器
【C/C++ 常用关键字使用指南】C++ 关键字 在头文件和源文件中函数声明与定义使用上的差异
【C/C++ 常用关键字使用指南】C++ 关键字 在头文件和源文件中函数声明与定义使用上的差异
88 0