Примечание: В объяснении и моем примере я использую библиотеку 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