简体   繁体   English

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

[英]Virtual functions or SFINAE for template specialisation… or a better way?

I'm writing a CFD application using Eigen for a lot of the calculations.我正在使用 Eigen 编写一个 CFD 应用程序进行大量计算。 Now I want to define a field class to hold the values for each variable.现在我想定义一个字段 class 来保存每个变量的值。 My current approach is to use a template class which is either instantiated for scalars and vectors or inherited by a ScalarField and a VectorField class and then mildly specialised.我目前的方法是使用模板 class ,该模板可以为标量和向量实例化,也可以由 ScalarField 和 VectorField class 继承,然后稍微专业化。 Scalars are stored in a 1 xn matrix, vectors in a 2 xn matrix (in 2D).标量存储在 1 xn 矩阵中,向量存储在 2 xn 矩阵(二维)中。

Now the problems arise when I want to access individual scalars or (column) vectors of those fields, as the return type of Eigen's access functions are expression templates, not the data type with which I instantiated the template.现在,当我想访问这些字段的单个标量或(列)向量时,问题就出现了,因为 Eigen 访问函数的返回类型是表达式模板,而不是我实例化模板的数据类型。 But I need to access them from inside the base class, otherwise I'd have a lot of duplicate code.但是我需要从基础 class 内部访问它们,否则我会有很多重复的代码。

So in the end, I found two approaches that worked, which both involve overloading the subscript operator [] :所以最后,我发现了两种可行的方法,它们都涉及重载下标运算符[]

  1. pure virtual functions in the base class template, with the return type being decided by a std::conditional , based on the data type with which the template is being instantiated, and then having rather readable overrides in the derived classes and基础 class 模板中的纯虚函数,返回类型由std::conditional决定,基于模板被实例化的数据类型,然后在派生类中有相当可读的覆盖和

  2. using SFINAE to define all functions inside the base class template, using std::enable_if , thus avoiding inheritance altogether.使用 SFINAE 定义基本 class 模板内的所有函数,使用std::enable_if ,从而完全避免 inheritance。 But those template functions aren't very readable.但是那些模板函数不是很可读。

As the return type of operator[] for vectors I used Eigen::Block<...> by the way.顺便说一下,作为向量的operator[]的返回类型,我使用了 Eigen::Block<...> 。

Here's a minimal working example, with the functions get() and get2() standing in for the virtual function approach and the SFINAE approach respectively, as well as int and double for scalars and vectors, so that it can be easily compiled.这是一个最小的工作示例,函数 get() 和 get2() 分别代表虚拟 function 方法和 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();
}

Now my question is: is there a more elegant and maintainable way to achieve my goal of template specialisation (and in the current case, accessing individual elements)?现在我的问题是:是否有更优雅和可维护的方式来实现我的模板专业化目标(在当前情况下,访问单个元素)? And if not, is one of the above ways preferable or can it be improved?如果不是,上述方法之一是可取的还是可以改进的? Any feedback welcome, I'm rather new to C++.欢迎任何反馈,我对 C++ 很陌生。

EDIT: As requested, here's something more similar to my actual code.编辑:根据要求,这里有一些更类似于我的实际代码的东西。

I'm using the subscript operator in setBoundaryPatch() and setInternalField and their overloads to access m_data .我在setBoundaryPatch()setInternalField及其重载中使用下标运算符来访问m_data Because for scalars, m_data[] works fine, but for vectors or blocks, I need to use m_data.col() or m_data.block() .因为对于标量, m_data[]工作正常,但对于向量或块,我需要使用m_data.col()m_data.block()

I attempted just using m_data.col() or m_data.block() for both scalars and vectors, but there's no conversion from a scalar to the respective expression.我尝试只对标量和向量使用m_data.col()m_data.block() ,但没有从标量转换为相应的表达式。 I guess I could use m_data.block(...) = T { value } , but wouldn't that construct a temporary object for each call and be rather slow?我想我可以使用m_data.block(...) = T { value } ,但这不会为每个调用构建一个临时的 object 并且相当慢吗?

Also, overloading the subscript operator just makes it rather convenient to work with the class.此外,重载下标运算符只会使使用 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

I'm still very open to answers and feedback, however, I at least found a less verbose and more elegant solution to what I had before, by is using if constexpr (C++17 is pretty awesome:):我仍然非常愿意接受答案和反馈,但是,我至少找到了一个更简洁、更优雅的解决方案,方法是使用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);
}

EDIT: What makes this solution even better, is that now, automatic type deduction actually works, reducing the code to编辑:使这个解决方案变得更好的是,现在,自动类型推导确实有效,将代码减少到

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: I was wrong about auto being usable. EDIT2:我对auto可用是错误的。 The.obj-files of my unit test executable simply still had the correct type stored, but they couldn't be recreated using auto .我的单元测试可执行文件的 .obj 文件仍然存储了正确的类型,但无法使用auto重新创建它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM