如何将 CRTP 中的模板化派生类与派生类表达式模板相结合?

问题描述

我的目标是实现一个向量类 Vec,它允许像 auto vecRes = vecA + vecB * vecC 这样的算术表达式的高效计算。这是一个已知问题,可以在维基百科上找到使用 CurIoUsly Recurring Template Pattern (CRTP) 的解决方案。

我开始采用硬编码向量元素类型 double 的实现,包括用于加法的派生类,

template <typename Exn>
class VecExn
{
public:
    double operator[] ( int32_t idx ) const { return static_cast<Exn const&>( *this )[idx]; }
    int32_t size() const { return static_cast<Exn const&>( *this ).size(); }
};

class Vec: public VecExn<Vec>
{
public:
    Vec() {}
    Vec( std::initializer_list<double> iniLis ) : eles_( iniLis ) {  }
    template < typename Exn1 >
    Vec( VecExn<Exn1> const& exn ) : eles_( exn.size() )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
    }
    template < typename Exn1 >
    Vec& operator=( VecExn<Exn1> const& exn )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
        return *this;
    }

    double operator[] ( int32_t idx ) const { return eles_[idx]; }
    double& operator[] ( int32_t idx ) { return eles_[idx]; }
    int32_t size() const { return eles_.size(); };
private:
    std::vector<double> eles_;
};


template <typename Lhs,typename Rhs>
class VecSum: public VecExn<VecSum<Lhs,Rhs> >
{
public:
    VecSum( Lhs const& lhs,Rhs const& rhs ) : lhs_( lhs ),rhs_( rhs ) {}
    double operator[] ( int32_t idx ) const { return lhs_[idx] + rhs_[idx]; }
    int32_t size() const { return lhs_.size(); };
private:
    Lhs const& lhs_;
    Rhs const& rhs_;
};

它应该可以正常工作。

但是,在我用模板替换 double方法中,基类获得了模板模板参数,而普通的 Vec 当然变成了模板化的。

template < typename Typ,template<typename> typename Exn >
class VecExn
{
public:
    Typ operator[] ( int32_t idx ) const { return static_cast<Exn<Typ> const&>( *this )[idx];}
    int32_t size() const { return static_cast<Exn<Typ> const&>( *this ).size(); }
};

template <typename Typ>
class Vec: public VecExn<Typ,Vec >
{
public:
    Vec() {}
    Vec( std::initializer_list<Typ> iniLis ) : eles_( iniLis ) {  }
    template < typename Exn1 >
    Vec( VecExn<Exn1> const& exn ) :eles_(exn.size() )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
    }
    template < typename Exn1 >
    Vec& operator=( VecExn<Exn1> const& exn )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
        return *this;
    }

    Typ operator[] ( int32_t idx ) const { return eles_[idx]; }
    Typ& operator[] ( int32_t idx ) { return eles_[idx]; }
    int32_t size() const { return eles_.size(); };
private:
    std::vector<Typ> eles_;
};

问题出现在 VecSum 类定义中,该类定义已经是硬编码 double 案例中的模板类,现在无法识别为 VecExn 的正确形式。

template <typename Typ,template <typename> typename Lhs,template <typename> typename Rhs>
class VecSum: public VecExn<Typ,VecSum<Typ,Lhs,Rhs> >  // ERROR: does not match the template parameter list for template parameter 'Exn'
{
public:
    VecSum( Lhs<Typ> const& lhs,Rhs<Typ> const& rhs ) : lhs_( lhs ),rhs_( rhs ) {}
    Typ operator[] ( int32_t idx ) const { return lhs_[idx] + rhs_[idx]; }
    int32_t size() const { return lhs_.size(); };
private:
    Lhs<Typ> const& lhs_;
    Rhs<Typ> const& rhs_;
};

我该如何解决这个问题?

解决方法

正如@super 和@Jarod42 所指出的,解决方案非常简单:

不要在基类或运算符的表达式模板中使用模板模板参数,而是将 double operator[] 的返回类型替换为 auto

template <typename Exn>
class VecExn
{
public:
    auto operator[] ( int32_t idx ) const { return static_cast<Exn const&>( *this )[idx]; }
    int32_t size() const { return static_cast<Exn const&>( *this ).size(); }
};

template <typename Typ>
class Vec: public VecExn<Vec<Typ>>
{
public:
    Vec() {}
    Vec( std::initializer_list<Typ> iniLis ) : eles_( iniLis ) {  }
    template < typename Exn1 >
    Vec( VecExn<Exn1> const& exn ) : eles_( exn.size() )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
    }
    template < typename Exn1 >
    Vec& operator=( VecExn<Exn1> const& exn )
    {
        for( int32_t idx = 0; idx < exn.size(); ++idx )
            eles_[idx] = exn[idx];
        return *this;
    }

    Typ operator[] ( int32_t idx ) const { return eles_[idx]; }
    Typ& operator[] ( int32_t idx ) { return eles_[idx]; }
    int32_t size() const { return eles_.size(); };
private:
    std::vector<Typ> eles_;
};


template <typename Lhs,typename Rhs>
class VecSum: public VecExn<VecSum<Lhs,Rhs> >
{
public:
    VecSum( Lhs const& lhs,Rhs const& rhs ) : lhs_( lhs ),rhs_( rhs ) {}
    auto operator[] ( int32_t idx ) const { return lhs_[idx] + rhs_[idx]; }
    int32_t size() const { return lhs_.size(); };
private:
    Lhs const& lhs_;
    Rhs const& rhs_;
};