Виртуальные функции или SFINAE для специализации шаблонов ... или лучший способ? - PullRequest
2 голосов
/ 27 сентября 2019

Я пишу CFD-приложение, использующее Eigen для многих расчетов.Теперь я хочу определить класс поля для хранения значений для каждой переменной.Мой текущий подход заключается в использовании шаблонного класса, который либо создается для скаляров и векторов, либо наследуется классами ScalarField и VectorField, а затем слегка специализируется.Скаляры хранятся в матрице 1 xn, векторы в матрице 2 xn (в 2D).

Теперь возникают проблемы, когда я хочу получить доступ к отдельным скалярам или (столбцам) векторам этих полей в качестве возвращаемого типа.из функций доступа Эйгена являются шаблоны выражений, а не тип данных, с которыми я инстанцировал шаблон.Но мне нужно получить к ним доступ из базового класса, иначе у меня было бы много дублирующегося кода.

Итак, в итоге я нашел два подходящих подхода, которые оба включают перегрузку оператора индекса []:

  1. чисто виртуальные функции в шаблоне базового класса, с типом возвращаемого значения, определяемым std::conditional, в зависимости от типа данных, с которым создается экземпляр шаблона, а затемс достаточно читаемыми переопределениями в производных классах и

  2. с использованием SFINAE для определения всех функций внутри шаблона базового класса с использованием std::enable_if, что позволяет полностью избежать наследования.Но эти шаблонные функции не очень читабельны.

В качестве типа возврата operator[] для векторов я, кстати, использовал Eigen :: Block <...>.

Вот минимальный рабочий пример, где функции get () и get2 () заменяют подход виртуальной функции и подход SFINAE соответственно, а также int и double для скаляров и векторов, так чтоможет быть легко скомпилирован.

#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

1 Ответ

0 голосов
/ 28 сентября 2019

Я по-прежнему очень открыт для ответов и отзывов, однако, по крайней мере, я нашел менее многословное и более элегантное решение того, что у меня было раньше, используя 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);
}

РЕДАКТИРОВАТЬ 2: я ошибся в autoбыть пригодным для использования..Obj-файлы моего исполняемого файла модульного теста просто сохраняли правильный тип, но они не могли быть воссозданы с использованием auto.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...