-1

I have a problem in C++, how to convert a vector<Data> into an vector<IData> implicitly.

the only answer i have about it is to create a new vector<IData> and copy each element of the vector<Data>.

I would like to know if there are elegant solutions in C++ to resolve this type of case.

Here is the code:

#include <iostream>
#include <vector>

using namespace std;

class IData
{
    public:
        virtual int getNumber() = 0;
};

class DataA : public IData
{
    public:
        DataA(int value) : _value(value) { }

        virtual int getNumber() override
        {
            return _value;
        }

    private:
        int _value = 0;
};

class DataB : public IData
{
    public:
        DataB(int value) : _value(value) { }

        virtual int getNumber() override
        {
            return _value;
        }

    private:
        int _value = 0;
};

int calculateDataSum(vector<IData> datas)
{
    int sum;
    for (int i = 0; i < datas.size(); i++)
    {
        sum += datas[i].getNumber();
    }
    return sum;
}

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);

    vector<DataA> datasA;
    datasA.push_back(dA0);
    datasA.push_back(dA1);

    vector<DataB> datasB;
    datasB.push_back(dB0);
    datasB.push_back(dB1);

    int resultA = calculateDataSum(datasA);
    int resultB = calculateDataSum(datasB);

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}
Hadou
  • 9
  • 4
  • 3
    How are `Data` and `IData` related? What are you going to do with the `vector`? – NathanOliver Jan 29 '19 at 17:10
  • 1
    An array of squares is not an array of rectangles. Also, if IData is an interface, it implies you should be using a vector of *pointers* instead of instantiated instances. Perhaps you could update your question to show the declarations for Data and IData. – selbie Jan 29 '19 at 17:13
  • 1
    concrete code is good, but include it in the question instead of linkling to it. It's clearly stated in the rules of this site, and you are much more likely to get some help if you follow those rules. – super Jan 29 '19 at 17:15
  • No matter, we could take a IVehicle and an Car / Skateboard ... and have a function (only one) able to take a list of IVehicle and avoid having a function (car) (skateboard) ... – Hadou Jan 29 '19 at 17:18
  • I'm kind of suspicious that you can't initialize private member variables in a class definition like that. I am also wondering how you think the process is going to handle your statement "int resultA = calculateDataSum(datasA);" and the next line when you have not provided the machinery to get the job done... – Dr t Jan 29 '19 at 17:28

4 Answers4

2

The most obvious approach since you already have a base class with virtual methods is to use a std::vector<std::unique_ptr<IData>>.

#include <memory>

int calculateDataSum(vector<std::unique_ptr<IData>>& datas)
{
    int sum;
    for (auto& data : datas)
    {
        sum += data->getNumber();
    }
    return sum;
}

int main()
{
    vector<std::unique_ptr<IData>> datasA;
    datasA.push_back(std::unique_ptr<IData>(new DataA(10)));
    datasA.push_back(std::unique_ptr<IData>(new DataA(20)));

    vector<std::unique_ptr<IData>> datasB;
    datasB.push_back(std::unique_ptr<IData>(new DataB(30)));
    datasB.push_back(std::unique_ptr<IData>(new DataB(40)));

    int resultA = calculateDataSum(datasA);
    int resultB = calculateDataSum(datasB);

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}

With this approach you could also mix, so you can have a vector with both DataA and DataB in it.

super
  • 12,335
  • 2
  • 19
  • 29
  • i like this approach too, but we need to have the data on the heap memory with this approach ? – Hadou Jan 29 '19 at 17:33
  • Yes. But a `std::vector` is already storing it's content on the heap. It is however contingous memory. When using pointers you lose that, so it could have an effect on cache locality if that is a concern. – super Jan 29 '19 at 17:36
  • Thanks for the precision on the vector – Hadou Jan 29 '19 at 17:42
  • Note that `push_back(new ...);` risks a memory leak if `new` succeeds but `push_back` fails. You should construct a `unique_ptr` to take ownership of the `new`'ed pointer before you then move it into the vector, eg: `unique_ptr d(new ...); push_back(move(d));` or `push_back(unique_ptr(new ...));` – Remy Lebeau Jan 29 '19 at 19:31
  • @LightnessRacesinOrbit It does cost extra, but it also gives some added flexibility. Looking strictly at the question example, that flexibility doesn't contribute. I guess just wanted to point out that performance cost is always relative. – super Jan 30 '19 at 12:12
  • @super Absolutely! Sometimes poisoned performance is acceptable in the face of competing advantages. But the performance _is_ poisoned nonetheless. – Lightness Races in Orbit Jan 30 '19 at 12:26
1

From C++ type system perspective, vector<Data> and vector<IData> are completely different types regardless of Data and IData hierarchical relationship.

One of solutions for your problem is template-based ad hoc polymorphism:

template<typename T>
int calculateDataSum(vector<T> datas)
{
    static_assert(std::is_base_of<IData, T>::value);
    int sum;
    for (int i = 0; i < datas.size(); i++)
    {
        sum += datas[i].getNumber();
    }
    return sum;
}

Note the static_assert line. It's not necessary, but it restricts allowed vector elements to chilren of IData.

Sergey
  • 7,985
  • 4
  • 48
  • 80
  • Thank for this !! that's the solution I'm looking for – Hadou Jan 29 '19 at 17:20
  • Yes, your solution is working well, but we need the "ISO C ++ 17 (/ std: c ++ 17)" standard. – Hadou Jan 29 '19 at 17:26
  • 1
    @Hadou You only need that standard for the `static_assert(condition)`. If you turn that into `static_assert(condition, "my message")` (or remove the `static_assert` line entirely) then everything is fine, no C++17 needed. – Max Langhof Jan 29 '19 at 17:32
  • Note that `datas` should be passed by (const) reference to avoid having to make a local copy of all the vector elements before calculating them together. Also, `sum` is uninitialized before entering the loop. – Remy Lebeau Jan 29 '19 at 19:29
0

This is my final code for solving my problem, thanks all

#include <iostream>
#include <vector>

using namespace std;

class IData
{
    public:
        virtual int getNumber() = 0;
};

class DataA : public IData
{
    public:
        DataA(int value) : _value(value) { }

        virtual int getNumber() override
        {
            return _value;
        }

    private:
        int _value = 0;
};

class DataB : public IData
{
    public:
        DataB(int value) : _value(value) { }

        virtual int getNumber() override
        {
            return _value;
        }

    private:
        int _value = 0;
};

class DataOther
{
    public:
        DataOther(int value) : _value(value) { }

        int getNumber()
        {
            return _value;
        }

    private:
        int _value = 0;
};

template<typename T>
int calculateDataSum(const vector<T> & datas)
{
    static_assert(std::is_base_of<IData, T>::value, "T does not inherit from IData");
    int sum = 0;
    for (int i = 0; i < datas.size(); i++)
    {
        sum += datas[i].getNumber();
    }
    return sum;
}

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);
    DataOther dO0(500);

    vector<DataA> datasA;
    datasA.push_back(dA0);
    datasA.push_back(dA1);

    vector<DataB> datasB;
    datasB.push_back(dB0);
    datasB.push_back(dB1);

    vector<DataOther> datasO;
    datasO.push_back(dO0);

    int resultA = calculateDataSum(datasA);
    int resultB = calculateDataSum(datasB);


    //Error because DataOther does not inherit IData
    //int resultO = calculateDataSum(datasO); 

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}
Hadou
  • 9
  • 4
  • 1
    Note that `datas` should be passed by (const) reference to avoid having to make a local copy of all the vector elements before calculating them together. – Remy Lebeau Jan 29 '19 at 19:35
0

Copying vector<DataA> and vector<DataB> elements into vector<IData> will not work because it suffers from object slicing. You need to use vector<IData*> instead to allow polymorphism to work correctly, eg:

int calculateDataSum(const vector<IData*> &datas)
{
    int sum = 0;
    for (auto d : datas)
    {
        sum += d->getNumber();
    }
    return sum;
}

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);

    vector<DataA> datasA;
    datasA.push_back(dA0);
    datasA.push_back(dA1);

    vector<DataB> datasB;
    datasB.push_back(dB0);
    datasB.push_back(dB1);

    vector<IData*> datas;
    transform(begin(datasA), end(datasA), back_inserter(datas),
        [](DataA &a) -> IData* { return &a; } 
    );
    int resultA = calculateDataSum(datas);

    datas.clear();
    transform(begin(datasB), end(datasB), back_inserter(datas),
        [](DataB &b) -> IData* { return &b; } 
    );
    int resultB = calculateDataSum(datas);

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}

Or, more simply:

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);

    vector<IData*> datasA;
    datasA.push_back(&dA0);
    datasA.push_back(&dA1);

    vector<IData*> datasB;
    datasB.push_back(&dB0);
    datasB.push_back(&dB1);

    int resultA = calculateDataSum(datasA);
    int resultB = calculateDataSum(datasB);

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}

Or, you could just get rid of the vectors altogether:

int calculateDataSum(initializer_list<IData*> datas)
{
    int sum = 0;
    for (auto d : datas)
    {
        sum += d->getNumber();
    }
    return sum;
}

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);

    int resultA = calculateDataSum({&dA0, &dA1});
    int resultB = calculateDataSum({&dB0, &dB1});

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}

Or even:

int calculateDataSum(const IData &arg)
{
    return arg.getNumber();
}

template <typename... Arguments>
int calculateDataSum(const IData &arg1, const Arguments&... args)
{
    return arg1.getNumber() + calculateDataSum(args...);
}

/* alternatively, in C++17 and later...
template <typename... Arguments>
int calculateDataSum(const IData &arg1, const Arguments&... args)
{
    int sum = arg1.getNumber();
    if constexpr(sizeof...(args) > 0)
    {
        sum += calculateDataSum(args...);
    }
    return sum;
}
*/

int main()
{
    DataA dA0(10);
    DataA dA1(20);
    DataB dB0(100);
    DataB dB1(200);

    int resultA = calculateDataSum(dA0, dA1);
    int resultB = calculateDataSum(dB0, dB1);

    cout << resultA << endl;
    cout << resultB << endl;

    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770