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

问题描述 投票:1回答:1

我正在使用Eigen编写CFD应用程序进行大量计算。现在,我想定义一个字段类来保存每个变量的值。我目前的方法是使用一个模板类,该模板类针对标量和向量实例化,或者由ScalarField和VectorField类继承,然后进行专门化处理。标量存储在1 x n矩阵中,向量存储在2 x n矩阵中(在2D中)。

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

因此,最后,我发现了两种可行的方法,都涉及重载下标运算符[]

  1. 基类模板中的纯虚函数,返回类型由std::conditional决定,基于实例化模板所使用的数据类型,然后在派生类中具有相当可读的覆盖,以及] >

  2. 使用SFINAE使用std::enable_if定义基类模板内的所有函数,从而完全避免继承。但是那些模板函数不是很可读。

  3. 作为向量的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 },但是那样会不会为每个调用构造一个临时对象并且速度很慢?

此外,重载下标运算符也使得使用该类非常方便。

#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

我正在使用Eigen编写CFD应用程序进行大量计算。现在,我想定义一个字段类来保存每个变量的值。我当前的方法是使用一个模板类,......>

c++ templates eigen return-type eigen3
1个回答
0
投票

我仍然非常乐于回答和反馈,但是,我至少使用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);
}
© www.soinside.com 2019 - 2024. All rights reserved.