CRTP接口:实现中的不同返回类型

问题描述 投票:1回答:1

注意:在说明和示例中,我正在使用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
c++ templates eigen return-type crtp
1个回答
0
投票

好吧,我认为我找到了一个可读性强的解决方案。仍然欢迎反馈。我刚刚定义了另一个模板参数bool,它告诉派生类是否保存数据,并使用std::conditionalbool定义了返回类型:

#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;
}

编译并执行正常。有点冗长,但至少清楚地表明了意图。

仍然欢迎其他答案。

© www.soinside.com 2019 - 2024. All rights reserved.