用于模板专业化的虚拟函数或 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,否则我会有很多重复代码。
所以最后,我发现了两种有效的方法,它们都涉及重载下标运算符 []
:
基础class模板中的纯虚函数,return类型由std::conditional
决定,基于模板的数据类型正在被实例化,然后在派生的 classes 和
中具有相当可读的覆盖
使用 SFINAE 在基本 class 模板中定义所有函数,使用 std::enable_if
,从而完全避免继承。但是那些模板函数可读性不是很好
作为 return 类型的 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 }
,但那样会不会为每次调用构造一个临时对象并且速度相当慢?
此外,重载下标运算符只会让使用 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
.
重新创建它们
我正在使用 Eigen 编写 CFD 应用程序进行大量计算。现在我想定义一个字段 class 来保存每个变量的值。 我当前的方法是使用一个模板 class,该模板要么为标量和向量实例化,要么由 ScalarField 和 VectorField class 继承,然后稍微专门化。 标量存储在 1 x n 矩阵中,向量存储在 2 x n 矩阵(二维)中。
现在,当我想访问这些字段的单个标量或(列)向量时,问题就出现了,因为 return 类型的 Eigen 访问函数是表达式模板,而不是我实例化的数据类型模板。但是我需要从基础内部访问它们class,否则我会有很多重复代码。
所以最后,我发现了两种有效的方法,它们都涉及重载下标运算符 []
:
基础class模板中的纯虚函数,return类型由
std::conditional
决定,基于模板的数据类型正在被实例化,然后在派生的 classes 和 中具有相当可读的覆盖
使用 SFINAE 在基本 class 模板中定义所有函数,使用
std::enable_if
,从而完全避免继承。但是那些模板函数可读性不是很好
作为 return 类型的 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 }
,但那样会不会为每次调用构造一个临时对象并且速度相当慢?
此外,重载下标运算符只会让使用 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
.