为什么在此 CRTP 基函数调用中添加引用会消除错误?

Why does adding a reference in this CRTP base function call remove errors?

我最近一直在尝试使用 Curiously Recurring Template Pattern 来实现各种矩阵 类 并且遇到了一些我不理解的行为。

当我有

    template <class T> double& MatrixBase<T>::operator()(int row, int col)
{
     return static_cast<T&>(*this).operator()(row, col);
}

我没有错误。如果我有

            template <class T> double& MatrixBase<T>::operator()(int row, int col)
{
    return static_cast<T>(*this).operator()(row, col);
}

我收到此错误消息:

src/matrices.cpp:15:12: error: no matching conversion for stati
c_cast from 'nav::MatrixBase<nav::Matrix>' to 'nav::Matrix'
    return static_cast<T>(*this).operator()(row, col);
           ^~~~~~~~~~~~~~~~~~~~~
src/matrices.cpp:122:24: note: in instantiation of member funct
ion 'nav::MatrixBase<nav::Matrix>::operator()' requested here
        dm(pos, pos) = operator()(pos, pos);
                       ^
src/matrices.cpp:636:23: note: in instantiation of member funct
ion 'nav::MatrixBase<nav::Matrix>::toDiagonalMatrix' requested here
        *this = thisAsMatrix.toDiagonalMatrix();
                             ^
src/matrices.h:85:7: note: candidate constructor (the implicit
copy constructor) not viable: no known conversion from 'nav::MatrixBase<nav::Mat
rix>' to 'const nav::Matrix' for 1st argument
class Matrix : public MatrixBase<Matrix>
      ^
src/matrices.cpp:199:9: note: candidate constructor not viable:
 requires 0 arguments, but 1 was provided
Matrix::Matrix() : rows_{}, cols_{}, data_{}
        ^
src/matrices.cpp:206:9: note: candidate constructor not viable:
 requires 2 arguments, but 1 was provided
Matrix::Matrix(int num_rows, int num_cols)

令我感到特别奇怪的是,我的 MatrixBase/Matrix 类 的其他成员没有 T 作为参考,但没有抛出任何错误。

下面是更多使用的代码:

template <class T> double& MatrixBase<T>::operator()(int row, int col)
{
    return static_cast<T&>(*this).operator()(row, col);
}

template <class T> double MatrixBase<T>::operator()(int row, int col) const
{
    return static_cast<T>(*this).operator()(row, col);
}

template <class T> T& MatrixBase<T>::operator=(double value)
{
    return static_cast<T>(*this).operator=(value);
}

template <class T> T& MatrixBase<T>::operator=(T& a)
{
    return static_cast<T>(*this).operator=(a);
}

// Add another derived Matrix to this derived Matrix
template <class T> T& MatrixBase<T>::operator+=(T& a)
{
    return static_cast<T>(*this).operator+=(a);
}


double& Matrix::operator()(int row, int col)
{
    REQUIRE(row >= 0 && col >= 0, "Error: Indices must be non-negative.");
    REQUIRE(row < rows_ && col < cols_, "Error: Element index outside matrix.");
    return data_[row][col];
}

double Matrix::operator()(int row, int col) const
{
    REQUIRE(row >= 0 && col >= 0, "Error: Indices must be non-negative.");
    REQUIRE(row < rows_ && col < cols_, "Error: Element index outside matrix.");
    return data_[row][col];
}

/*****************************************************************************
 *   Purpose:  Initializes the whole matrix to the specified value.
 ******************************************************************************/
Matrix& Matrix::operator=(double value)
{
    for(int i = 0; i < rows_; i++)
    {
        for(int j = 0; j < cols_; j++)
        {
            data_[i][j] = value;
        }
    }
    return *this;
}

/*****************************************************************************
*   Purpose:  Copies one matrix to another.
******************************************************************************/
Matrix& Matrix::operator=(const Matrix& a)
{
    int iteratedRows = 0;
    while(iteratedRows < a.rows())
    {
        if(this->rows() - 1 < iteratedRows)
        {
            this->data_.push_back(pfstl::VectorN<double, kMaxDimension>());
            this->rows_++; // we added a new row
        }
        int iteratedCols = 0;
        while(iteratedCols < a.cols())
        {
            if(this->cols() - 1 < iteratedCols)
            {
                this->data_[iteratedRows].push_back(a(iteratedRows, iteratedCols));
            }
            else
            {
                this->data_[iteratedRows][iteratedCols] = a(iteratedRows, iteratedCols);
            }
            iteratedCols++;
        }
        iteratedRows++;
    }
    // if a has less rows than this, force this to have the same # of rows as a
    while(a.rows() < this->rows_)
    {
        // In order to make the sizes equal, remove excess rows from this from the back
        this->data_.erase(end(this->data_));
        this->rows_--;
    }
    // Same logic as above for columns
    while(a.cols() < this->cols_)
    {
        for(int i = 0; i < this->rows_; i++)
        {
            this->data_[i].erase(end(this->data_[i]));
        }
        this->cols_--;
    }

    this->rows_ = a.rows();
    this->cols_ = a.cols();

    return *this;
}

// Add another derived Matrix to this derived Matrix
Matrix& Matrix::operator+=(const Matrix& a)
{

    REQUIRE(rows_ == a.rows(), "Error: Matrix addition requires same number of rows.");
    REQUIRE(cols_ == a.cols(), "Error: Matrix addition requires same number of columns.");

    for(int i = 0; i < a.rows(); i++)
    {
        for(int k = 0; k < a.cols(); k++)
        {
            data_[i][k] += a(i, k);
        }
    }

    return *this;
}

头文件:

template <typename T> class MatrixBase
{
public:
    // These methods must be implemented for each deriving class

    double& operator()(int row, int col);
    double operator()(int row, int col) const;

    T& operator=(double value);
    T& operator=(T& a);

    // Add another derived Matrix to this derived Matrix
    T& operator+=(T& a);
}

class Matrix : public MatrixBase<Matrix>
{
public:
    // Constructors
    Matrix();
    Matrix(int row, int cols);
    // Destructor
    ~Matrix();

    double& operator()(int row, int col);
    double operator()(int row, int col) const;

    Matrix& operator=(double value);
    Matrix& operator=(const Matrix& a);

    // Add another derived Matrix to this derived Matrix
    Matrix& operator+=(const Matrix& a);
}

转换为值类型会创建一个新对象,在本例中需要 Matrix 构造函数采用 MatrixBase。您的代码中不存在这样的构造函数。

转换为引用类型只会为您提供以另一种方式引用的原始对象。

我只能想象其他函数不会产生相同的错误,因为它们从未被实例化,但我承认我没有通读你所有的(很长的)示例代码。

这是一个简短的测试用例,从根本上演示了转换行为(尽管转换为相同的 class,因此请注意复制构造函数):

struct NotCopyable
{
    NotCopyable() {}
    NotCopyable(const NotCopyable&) = delete;
};

template <typename T>
void foo(T t)
{
    (void)static_cast<T>(t);
}

template <typename T>
void bar(T& t)
{
    (void)static_cast<T&>(t);
}

int main()
{
    NotCopyable c;
    foo(c);
    bar(c);
}

它失败并出现同样的错误,但如果您注释掉 foo 则它会成功,因为不再需要实例化该函数模板。 bar总是成功。

这里又是那个例子,这次修改为更接近您的原始代码,通过在 Derived 中引入继承关系和 missing 候选构造函数:

struct Base
{
    virtual ~Base() {}
};

struct Derived : Base {};

template <typename T>
void foo(T t)
{
    (void)static_cast<Derived>(t);
}

template <typename T>
void bar(T& t)
{
    (void)static_cast<Derived&>(t);
}

int main()
{
    Derived c;
    foo<Base>(c);
    bar<Base>(c);
}

再次注释掉 foo<Base>(c) 调用,整个程序将生成。

具有讽刺意味的是构造函数并不是真的 "missing" 因为它不应该存在;只是您一开始就不应该尝试创造新的价值。引用的转换是正确的。