使 double 和 std::vector<double> 协变

Posted

技术标签:

【中文标题】使 double 和 std::vector<double> 协变【英文标题】:Making double and std::vector<double> covariant 【发布时间】:2020-02-27 13:51:18 【问题描述】:

我正在尝试为“任何”数据类型制作一个包装器,以便它们具有称为IValue 的公共接口,因此可以在任何具体值上调用get() 并返回具体数据类型的值。在最简单的情况下,我只想能够在doublestd::vector&lt;double&gt; 上调用get()。据我了解,这些数据类型需要是协变的(在我的代码中根本没有)。以下是我对代码的原始想象:

//template<typename T>
class IValue 

protected:
    // typedef std::variant<T, std::vector<T>> return_type; <- this was an approach
public:
    IValue() 

    virtual int size() = 0;

    virtual /*some special type*/ get() = 0;
;


template<typename T>
class Scalar : public IValue<T> 
    T data = NULL;
public:

    Scalar(T new_data) : data(new_data) 

    T get()  return data; 

    int size()   return 1; 
;


template<typename T>
class Vector : public IValue<T> 

    std::vector<T> data;
public:

    Vector(std::vector<T> new_data) : data(new_data) 

    std::vector<T> get()  return data; 

    T get_element(int index)  return data[index]; 

    int size()   return data.size(); 
;

我在 VS17 上使用 c++17。

【问题讨论】:

你不能用运行时多态来做到这一点,因为没有办法使这些类型协变。我建议研究一下 CRTP,它确实编译时多态性并允许您返回不同的类型。 IValuestd::span 还是 1 个或多个 double? (顺便说一句,在这种情况下也不需要继承)。 @Jarod42 不完全是,IValue 应该为一个类提供一个特定类型的值,以及一些“具有值”的功能,例如寻找它是否“设置”或以防万一矢量询问其大小。 【参考方案1】:

你确实快到了。 std::variant&lt;T, std::vector&lt;T&gt;&gt; 确实是正确的返回类型,您的 get() return data; 实现也是正确的。从字面上看,唯一的大问题是所有get 覆盖的返回类型应该是std::variant&lt;T, std::vector&lt;T&gt;&gt;

样式方面,get 应该是 const,添加 override 有助于改进错误消息。

【讨论】:

【参考方案2】:

协方差仅适用于多态类型的指针/引用。

所以你必须在某个类中包装double/vector&lt;double&gt;

struct IWrapper

    virtual ~IWrapper() = default;
;

struct DoubleWrapper : IWrapper

    double d = 0.;
;

struct DoubleVecWrapper : IWrapper

    std::vector<double> v;
;

然后你可以有类似的东西:

class IValue 

public:
    virtual ~IValue() = default

    virtual int size() = 0;
    virtual IWrapper& get() = 0;
;


class Scalar : public IValue 
    DoubleWrapper data;
public:
    explicit Scalar(double d) : data(d) 

    T& get() override  return data; 
    int size() override  return 1; 
;


class Vector : public IValue

    DoubleVecWrapper data;
public:

    Vector(const std::vector<T>& v) : data(v) 

    DoubleVecWrapper& get() override  return data; 
    int size() override  return data.v.size(); 

    T get_element(int index)  return data[index]; 
;

但没有有意义的接口,多半是没用的

// No use of Base interface or inheritance, so ok
void foo(Scalar& value)

    const auto size = value.size();
    auto& wrapper = value.get(); // auto& is DoubleWrapper&
    wrapper.d = 4.2;


// But, with base class, we cannot go really far:
void foo(IValue& value)

    const auto size = value.size();
    auto& wrapper = value.get(); // auto& is IWrapper&
    // What to do now? IWrapper can't do anything

【讨论】:

我认为我的问题是我尝试创建一个模板机制来完美地处理任何数据类型,我正在寻找一个完美的解决方案,这似乎是不可能的(在可用性和优雅)在这种情况下。

以上是关于使 double 和 std::vector<double> 协变的主要内容,如果未能解决你的问题,请参考以下文章

带有 std::function<double(std::vector<double>) 回调的 cppyy

如何将 std::vector<std::vector<double>> 转换为 torch::Tensor?

使用 std::count_if() 时出现错误“没有从 'std::vector<double, std::allocator<double> >' 到 'double *'

如何在初始化中将 'std::vector<double>' 转换为 'double'

如何将 std::vector<std::vector<double>> 转换为 Rcpp::Dataframe 或 Rcpp::NumericMatrix

使用“auto”代替 std::vector<double>*