Я пишу CFD-приложение, использующее Eigen для многих расчетов.Теперь я хочу определить класс поля для хранения значений для каждой переменной.Мой текущий подход заключается в использовании шаблонного класса, который либо создается для скаляров и векторов, либо наследуется классами ScalarField и VectorField, а затем слегка специализируется.Скаляры хранятся в матрице 1 xn, векторы в матрице 2 xn (в 2D).
Теперь возникают проблемы, когда я хочу получить доступ к отдельным скалярам или (столбцам) векторам этих полей в качестве возвращаемого типа.из функций доступа Эйгена являются шаблоны выражений, а не тип данных, с которыми я инстанцировал шаблон.Но мне нужно получить к ним доступ из базового класса, иначе у меня было бы много дублирующегося кода.
Итак, в итоге я нашел два подходящих подхода, которые оба включают перегрузку оператора индекса []
:
чисто виртуальные функции в шаблоне базового класса, с типом возвращаемого значения, определяемым std::conditional
, в зависимости от типа данных, с которым создается экземпляр шаблона, а затемс достаточно читаемыми переопределениями в производных классах и
с использованием 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