注意:
在说明和示例中,我正在使用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
答案 0 :(得分:0)
好的,我想我找到了一个可读性强的解决方案。仍然欢迎反馈。
我刚刚定义了另一个模板参数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;
}
编译并执行正常。较为详细,但至少清楚地表明了意图。
仍然欢迎其他答案。
答案 1 :(得分:0)
注意:添加另一个答案,因为两个答案均有效且独立。
另一种方法是定义特征类。与上一个答案相比的优势在于,任何想要使用Base
类型的对象的代码都不必具有多个模板参数。多个模板参数意味着它们可以混合和匹配,但是这里的情况更多是关于一种模板类型,在逻辑上暗示应该使用哪种类型。
首先,定义一个空的traits类:
template<typename T>
class BaseTraits {};
这是完整定义,而不是前向声明。然后,它必须专门针对从Base
派生的每种类型:
class Derived1; // forward declaration for the traits class
template<>
class BaseTraits<Derived1>
{
public:
using VectorType = Matrix2Xd::ConstColXpr;
};
和
class Derived2;
template<>
class BaseTraits<Derived2>
{
public:
using VectorType = Eigen::Vector2d;
};
现在,Base
可以将VectorType
与typealias一起使用:
template<typename T>
class Base
{
public:
using VectorType = typename BaseTraits<T>::VectorType;
auto myFunc(int) const -> VectorType; /* note the speaking return type */
protected:
Base();
};
现在可以清楚地知道myFunc
应该返回什么-至少与特征的命名一样清晰;)
这是完整的代码:
#include <Eigen/Dense>
#include <type_traits>
#include <utility>
#include <iostream>
template<typename T>
class BaseTraits {};
template<typename T>
class Base
{
public:
using VectorType = typename BaseTraits<T>::VectorType;
auto myFunc(int) const -> VectorType;
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 -> VectorType {
return static_cast<const T&>(*this).myFuncImp(i);
}
using Matrix2Xd = Eigen::Matrix<double,2,Eigen::Dynamic>;
class Derived1;
template<>
class BaseTraits<Derived1>
{
public:
using VectorType = Matrix2Xd::ConstColXpr;
};
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;
template<>
class BaseTraits<Derived2>
{
public:
using VectorType = Eigen::Vector2d;
};
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;
}