注意:在说明和示例中,我正在使用eigen
库。但是,我的问题可能会被不熟悉该库的人员(例如,通过将ConstColXpr
替换为std::string_view
,并将Vector
替换为std::string
。
问题:我想使用CRTP创建一个接口,其中有两个继承自该类的类,在调用某些成员函数时,它们具有以下不同之处:
Eigen::Matrix<...>::ConstColXpr
)的视图Eigen::Vector<...>
)时计算适当的值两种返回类型都具有相同的尺寸(例如2x1的列向量)和相同的接口,即可以完全相同的方式进行交互。这就是为什么我认为将函数定义为接口的一部分是合理的。但是,我不知道如何在基类/接口中正确定义/限制返回类型。 auto
可以很好地编译和执行,但不告诉用户任何有关期望的内容。
是否可以以更清晰的方式定义接口?我尝试将std::invoke_result
与实现函数一起使用,但是随后我必须在接口之前包括继承类型,这是相当落后的。它并没有比auto
好多少,因为实际类型仍然需要在实现中查找。
一个很好的答案是常见的Eigen
类型,其中尺寸清晰。但是,我不希望调用接口函数需要模板参数(这与Eigen::MatrixBase
有关),因为已经有依赖于接口的代码了。另一个不错的答案是某种构造,它允许两种不同的返回类型,而不必知道完整的派生类型。但是欢迎所有答案和其他反馈!
这是说明问题的代码:
#include <Eigen/Dense>
#include <type_traits>
#include <utility>
#include <iostream>
template<typename T>
class Base
{
public:
auto myFunc(int) const;
protected:
Base();
};
template<typename T>
Base<T>::Base() {
/* make sure the function is actually implemented, otherwise generate a
* useful error message */
static_assert( std::is_member_function_pointer_v<decltype(&T::myFuncImp)> );
}
template<typename T>
auto Base<T>::myFunc(int i) const {
return static_cast<const T&>(*this).myFuncImp(i);
}
using Matrix2Xd = Eigen::Matrix<double,2,Eigen::Dynamic>;
class Derived1 : public Base<Derived1>
{
private:
Matrix2Xd m_data;
public:
Derived1( Matrix2Xd&& );
private:
auto myFuncImp(int) const -> Matrix2Xd::ConstColXpr;
friend Base;
};
Derived1::Derived1( Matrix2Xd&& data ) :
m_data {data}
{}
auto Derived1::myFuncImp(int i) const -> Matrix2Xd::ConstColXpr {
return m_data.col(i);
}
class Derived2 : public Base<Derived2>
{
private:
auto myFuncImp(int) const -> Eigen::Vector2d;
friend Base;
};
auto Derived2::myFuncImp(int i) const -> Eigen::Vector2d {
return Eigen::Vector2d { 2*i, 3*i };
}
int main(){
Matrix2Xd m (2, 3);
m <<
0, 2, 4,
1, 3, 5;
Derived1 d1 { std::move(m) };
std::cout << "d1: " << d1.myFunc(2).transpose() << "\n";
Derived2 d2;
std::cout << "d2: " << d2.myFunc(2).transpose() << "\n";
return 0;
}
在我的机器上,此打印
d1: 4 5
d2: 4 6
好吧,我认为我找到了一个可读性强的解决方案。仍然欢迎反馈。我刚刚定义了另一个模板参数bool
,它告诉派生类是否保存数据,并使用std::conditional
和bool
定义了返回类型:
#include <Eigen/Dense>
#include <type_traits>
#include <utility>
#include <iostream>
using Matrix2Xd = Eigen::Matrix<double,2,Eigen::Dynamic>;
using Eigen::Vector2d;
template<typename T, bool hasData>
class Base
{
public:
auto myFunc(int) const ->
std::conditional_t<hasData, Matrix2Xd::ConstColXpr, Vector2d>;
protected:
Base();
};
template<typename T, bool hasData>
Base<T, hasData>::Base() {
static_assert( std::is_member_function_pointer_v<decltype(&T::myFuncImp)> );
}
template<typename T, bool hasData>
auto Base<T, hasData>::myFunc(int i) const ->
std::conditional_t<hasData, Matrix2Xd::ConstColXpr, Vector2d> {
return static_cast<const T&>(*this).myFuncImp(i);
}
class Derived1 : public Base<Derived1, true>
{
private:
Matrix2Xd m_data;
public:
Derived1( Matrix2Xd&& );
private:
auto myFuncImp(int) const -> Matrix2Xd::ConstColXpr;
friend Base;
};
Derived1::Derived1( Matrix2Xd&& data ) :
m_data {data}
{}
auto Derived1::myFuncImp(int i) const -> Matrix2Xd::ConstColXpr {
return m_data.col(i);
}
class Derived2 : public Base<Derived2, false>
{
private:
auto myFuncImp(int) const -> Eigen::Vector2d;
friend Base;
};
auto Derived2::myFuncImp(int i) const -> Eigen::Vector2d {
return Eigen::Vector2d { 2*i, 3*i };
}
int main(){
Matrix2Xd m (2, 3);
m <<
0, 2, 4,
1, 3, 5;
Derived1 d1 { std::move(m) };
std::cout << "d1: " << d1.myFunc(2).transpose() << "\n";
Derived2 d2;
std::cout << "d2: " << d2.myFunc(2).transpose() << "\n";
return 0;
}
编译并执行正常。有点冗长,但至少清楚地表明了意图。
仍然欢迎其他答案。