用于模板专业化的虚拟函数或 SFINAE ......或更好的方法?

Virtual functions or SFINAE for template specialisation... or a better way?

我正在使用 Eigen 编写 CFD 应用程序进行大量计算。现在我想定义一个字段 class 来保存每个变量的值。 我当前的方法是使用一个模板 class,该模板要么为标量和向量实例化,要么由 ScalarField 和 VectorField class 继承,然后稍微专门化。 标量存储在 1 x n 矩阵中,向量存储在 2 x n 矩阵(二维)中。

现在,当我想访问这些字段的单个标量或(列)向量时,问题就出现了,因为 return 类型的 Eigen 访问函数是表达式模板,而不是我实例化的数据类型模板。但是我需要从基础内部访问它们class,否则我会有很多重复代码。

所以最后,我发现了两种有效的方法,它们都涉及重载下标运算符 []:

  1. 基础class模板中的纯虚函数,return类型由std::conditional决定,基于模板的数据类型正在被实例化,然后在派生的 classes 和

  2. 中具有相当可读的覆盖
  3. 使用 SFINAE 在基本 class 模板中定义所有函数,使用 std::enable_if,从而完全避免继承。但是那些模板函数可读性不是很好

作为 return 类型的 operator[] 顺便说一下,我使用 Eigen::Block<...> 向量。

这是一个最小的工作示例,函数 get() 和 get2() 分别代表虚函数方法和 SFINAE 方法,intdouble 代表标量和向量,以便于编译。

#include <iostream>
#include <type_traits>

template<typename T>
class Base
{
protected:
    T m_data;

public:
    Base(T t) :
        m_data {t}
    {}

    virtual typename std::conditional<
        std::is_same<T, double>::value, int, T>::type
    get() = 0;

    template<typename U = T>
    std::enable_if_t<std::is_integral<U>::value,T> get2(){
        return m_data;
    }
    template<typename U = T>
    std::enable_if_t<std::is_floating_point<U>::value,int> get2(){
        return m_data;
    }

    void print() {
        std::cout << this->get() << "\n";
        std::cout << this->get2() << "\n";
    }
};

class Int : public Base<int>
{
public:
    Int(int i) :
        Base(i)
    {}

    virtual int get() override
    { return m_data; }
};

class Double : public Base<double>
{
public:
    Double(double d) :
        Base<double>(d)
    {}

    virtual int get() override
    { return m_data; }
};

int main()
{
    Int i { 1 };
    i.print();

    Double d { 1.1 };
    d.print();

    //Base<int> b1 { 1 };
    //b1.print();

    //Base<double> b2 { 1.1 };
    //b2.print();
}

现在我的问题是:是否有更优雅和可维护的方式来实现我的模板专业化目标(在当前情况下,访问单个元素)? 如果不是,上述方法中的一种更可取还是可以改进? 欢迎任何反馈,我是 C++ 的新手。

编辑:根据要求,这里有一些与我的实际代码更相似的东西。

我在 setBoundaryPatch()setInternalField 中使用下标运算符及其重载来访问 m_data。因为对于标量,m_data[] 工作正常,但对于向量或块,我需要使用 m_data.col()m_data.block().

我尝试对标量和向量都使用 m_data.col()m_data.block(),但是没有从标量到相应表达式的转换。我想我可以使用 m_data.block(...) = T { value },但那样会不会为每次调用构造一个临时对象并且速度相当慢?

此外,重载下标运算符只会让使用 class.

变得相当方便
#include <Eigen/Dense>
#include <type_traits>
#include "mesh.h"

namespace fvm
{

constexpr auto dims { 2 }; // dimensions
using vector = Eigen::Matrix<double, dims, 1>;
using field  = Eigen::Matrix<double, n, Eigen::Dynamic>;


template<typename T>
class Field
{
protected:
    static constexpr int n {
        (std::is_same<T, double>::value ? 1 : fvm::dims ) };

    /* because of some more member variables than just the data, e.g. a
     * reference to the mesh, I don't want to use an Eigen type directly. */
    const fvm::Mesh& m_mesh;
    fvm::field<n>    m_data;

public:
    Field( const fvm::Mesh& mesh ) :
        m_mesh { mesh }
    {
        m_data.resize( Eigen::NoChange, m_mesh.cellCount() );
    }

    const fvm::field<n>& data() const
    { return m_data; }

    fvm::field<n>& data()
    { return m_data; }

    /* sets the field values belonging to the ghost cells of a boundary patch to
     * a uniform value */
    void setBoundaryPatch( const std::string& patchName, T )
    {
        for ( auto faceInd : m_mesh.boundaryPatch(patchName) )
            (*this)[ m_mesh.face(faceInd).ghostCell().ID() ] = t;
    }
    /* and a couple overloads for non-uniform values */

    /* sets the field values belonging to domain cells to a uniform value */
    void setInternalField( T );
    /* and a couple overloads for non-uniform values */

protected:
    using col      =       Eigen::Block<       fvm::field<n> >;
    using constCol = const Eigen::Block< const fvm::field<n> >;

public:
    /* using SFINAE to define subscript operator[] */
    //template<typename U = T>
    //std::enable_if_t<!std::is_same<U, double>::value, constCol>
    //operator[] (int i) const {
    //  return m_data.block(0, i, n, 1);
    //}

    //template<typename U = T>
    //std::enable_if_t<!std::is_same<U, double>::value, col>
    //operator[] (int i) {
    //  return m_data.block(0, i, n, 1);
    //}


    //template<typename U = T>
    //std::enable_if_t<std::is_same<U, double>::value, T>
    //operator[] (int i) const {
    //  return m_data[i];
    //}

    //template<typename U = T>
    //std::enable_if_t<std::is_same<U, double>::value, T&>
    //operator[] (int i) {
    //  return m_data[i];
    //}

    /* using pure virtual functions to overload the subscript operator[] */
    virtual typename std::conditional<
        std::is_same<T, fvm::vector>::value, constCol, T>::type
    operator[] (int) const = 0;

    virtual typename std::conditional<
        std::is_same<T, fvm::vector>::value, col, T&>::type
    operator[] (int) = 0;


    virtual void readFromFile( const std::string& path ) = 0;
    virtual void writeToFile( const std::string& path ) const = 0;
};

/* if I defined everything in the template, I could just declare aliases 
 * -> SFINAE option*/
//using ScalarField = Field<fvm::scalar>;
//using VectorField = Field<fvm::vector>;

/* or I define them as classes and let them inherit from Field<> */
class ScalarField : public Field<fvm::scalar>
{
public:
    ScalarField( const fvm::Mesh& mesh );

    virtual fvm::scalar  operator[] (int) const override;
    virtual fvm::scalar& operator[] (int)       override;

    virtual void writeToFile(  const std::string& path ) const override;
    virtual void readFromFile( const std::string& path )       override;
};

class VectorField : public Field<fvm::vector>
{
public:
    VectorField( const fvm::Mesh& );

    virtual VectorField::constCol operator[] (int) const override;
    virtual VectorField::col      operator[] (int)       override;

    virtual void writeToFile(  const std::string& path ) const override;
    virtual void readFromFile( const std::string& path )       override;
};

} // end namespace fvm

我仍然对答案和反馈持开放态度,但是,我至少找到了一个更简洁、更优雅的解决方案,方法是使用 if constexpr(C++17 非常好太棒了!):

using col       =       Eigen::Block<       fvm::field<n> >;
using constCol  = const Eigen::Block< const fvm::field<n> >;
using elem      = typename std::conditional<n==1, fvm::scalar&, col     >::type;
using constElem = typename std::conditional<n==1, fvm::scalar , constCol>::type;

elem      operator[] (int) {
    if constexpr ( n==1 )
        return m_data[i];
    else
        return m_data.block(0, i, n, 1);
}
constElem operator[] (int) const {
    if constexpr ( n==1 )
        return m_data[i];
    else
        return m_data.block(0, i, n, 1);
}

编辑:让这个解决方案更好的是,现在,自动类型推导实际上起作用了,将代码减少到

auto operator[] (int) {
    if constexpr ( n==1 )
        return m_data[i];
    else
        return m_data.block(0, i, n, 1);
}
auto operator[] (int) const {
    if constexpr ( n==1 )
        return m_data[i];
    else
        return m_data.block(0, i, n, 1);
}

EDIT2:我错了 auto 可用。我的单元测试可执行文件的 .obj 文件仍然存储了正确的类型,但无法使用 auto.

重新创建它们