Делаем double и std :: vector <double>ковариантными - PullRequest
0 голосов
/ 27 февраля 2020

Я пытаюсь создать оболочку для «любого» типа данных так, чтобы у них был общий интерфейс с именем IValue, чтобы можно было вызвать get() для любого конкретного значения и вернуть значение конкретного типа данных. В простейшем случае я просто хочу иметь возможность звонить get() на double и std::vector<double>. В моем понимании эти типы данных должны быть ковариантными (чего нет в моем коде). Вот мое сырое воображение кода:

//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(); }
};

Я использую c ++ 17 на VS17.

Ответы [ 2 ]

1 голос
/ 27 февраля 2020

Ты действительно почти там. std::variant<T, std::vector<T>> действительно правильный тип возвращаемого значения, и ваши get() { return data; } реализации также верны. Буквально единственная большая проблема заключается в том, что тип возвращаемого значения всех переопределений get должен быть std::variant<T, std::vector<T>>.

В стиле get должно быть const, а добавление override помогает улучшить сообщения об ошибках.

0 голосов
/ 27 февраля 2020

Ковариация применяется только к указателю / справочнику полиморфных c типов.

, поэтому вам нужно обернуть double / vector<double> в некоторый класс:

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
}
...