-1

Hello I am new to the c++ and I got stuck when trying to sort vector of unique pointers. I have a base class and derived classes (here is one).

class Item
{
public:
    virtual void printData() {}
};

class IntItem : public Item
{

public:
    int data;
    IntItem(int data)
    {
        this->data = data;
    }
    void printData()
    {
        std::cout << data;
    }
};
struct intItemCompare
{
    inline bool operator()(std::unique_ptr<IntItem>& a, std::unique_ptr<IntItem>& b)
    {
        return a->data < b->data;
    }
};

and I have std::vector<std::unique_ptr<Item>> data; where are stored uniqe pointers of objects of derived classes - NOT Item class (e.g. IntItem object)

Data are pushed to the vecor like this:

std::unique_ptr<IntItem> newItem= std::make_unique<IntItem>(10); // 10 is just random number
data.push_back(std::move(newItem));

And I want to sort it with std::sort(data.begin(),data.end(),intItemCompare()); And one of errors is
no known conversion for argument 1 from ‘std::unique_ptr<Item>’ to ‘std::unique_ptr<IntItem>&’ 41 | inline bool operator()(std::unique_ptr<IntItem>& a, std::unique_ptr<IntItem>& b)

I can add whole error message if needed.

How should I implement it ? Am I doing it totally wrong ? Thanks for any help.

Riomare
  • 65
  • 1
  • 10
  • You should show the declaration for `data`. Please read about [MCVE]. – François Andrieux Jan 05 '22 at 18:14
  • A `std::unique_ptr` is not the same type as `std::unique_ptr`, even if `IntItem` is derived from `Item`. – PaulMcKenzie Jan 05 '22 at 18:15
  • 2
    If you have a a container of `Item`s rather than `intItem`s does it make sense to sort them? What if there are `stringItem`s in that container? How do you order a string with respect to an integer? – user4581301 Jan 05 '22 at 18:16
  • @FrançoisAndrieux What do you mean by declaration ? Isn't information how items are pushed to the data enough ? – Riomare Jan 05 '22 at 18:19
  • @user4581301 well that is exactly what I have .. stringItems are also in that vector. But I don't know what are you trying to say. – Riomare Jan 05 '22 at 18:20
  • @Riomare The error message shows the container holds elements of a type that is different from the type you are passing, and different from the type accepted by your comparator. You should try to provide minimal but complete code samples to avoid any ambiguity or guesswork and help users provide complete answers. – François Andrieux Jan 05 '22 at 18:20
  • @Riomare Consider what happens if your vector has both `IntItem` and `StringItem`. How is `intItemCompare` meant to sort them? Only a comparator that is compatible with the container's element type can be used. – François Andrieux Jan 05 '22 at 18:21
  • @FrançoisAndrieux Well for StringItem I will have another strItemCompare – Riomare Jan 05 '22 at 18:22
  • @Riomare -- Create a virtual function in `Item` that does the comparison of two `Item` types. Derived classes implement it, and that function is called from `std::sort`. Basically add a layer of indirection. – PaulMcKenzie Jan 05 '22 at 18:23
  • 2
    @Riomare `std::sort` doesn't know what kind of `Item` the vector's elements point to, it can only assume those pointed objects are derived from `Item`. It requires that your comparator accept pointers to `Item`, not to some derived type. – François Andrieux Jan 05 '22 at 18:25
  • @Riomare Neither `intItemCompare` nor `strItemCompare` can compare an `IntItem` to a `StringItem`. You need a generic `itemCompare` that can accept _any_ type of item and compare it to any other type of item. – Miles Budnek Jan 05 '22 at 18:26
  • You need to have one set of sorting rules that can support all of the possible types in the container against one another. Given a string and an int, which goes first in the sorted container? Given an int and a Foo, which goes first? How about a Foo and a string? See the problem? – user4581301 Jan 05 '22 at 18:27
  • @Riomare -- How did you expect this to work in the long run? You say you have an `intCompare`, `stringCompare`, and whatever else. So your code would have been "if it's a string, do a string compare, if it's an int, do an int compare, if it's a widget, do a widget compare," etc... See how far down the rabbit hole this goes? Maybe you should redesign your code, or structure it so that it doesn't have to do this type of logic. – PaulMcKenzie Jan 05 '22 at 18:30
  • Handy reading: [What is an example of the Liskov Substitution Principle?](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle). TL;DR version: A derived class must be able to function as a Base class. If you can't guarantee this, rethink inheritance. Perhaps a container of a single, generic type like serializing to string may be better suited to your use case. – user4581301 Jan 05 '22 at 18:37
  • `... bool operator()(std::unique_ptr const& a, std::unique_ptr const& b) { return static_cast(*a).data < static_cast(*b).data; } ...` but this does have a code smell, but given your restrictions, I've no idea how to avoid this... If there are items other than `IntItems` in `data`, you've got undefined behaviour though. Also the `Item` class is missing a virtual destructor... – fabian Jan 05 '22 at 18:39

1 Answers1

1

I would change a few things in your code:

  • If you want to treat your items polymorphically, you should create heap instances of the derived classes, e.g. IntItem, but then use a vector of pointers to the base class, Item.
  • In order to sort items based on the item's data, you could provide a virtual method getData().
  • I would definitely not use unique_ptrs in the compare function, but just raw pointers; std::ranges projections fit well here because you can tell sort to first get the raw pointer to each element and the pass it to the compare function.

[Demo]

#include <algorithm>  // sort
#include <iostream>  // cout
#include <memory>  // make_unique, unique_ptr
#include <vector>

class Item
{
public:
    virtual int getData() = 0;
    virtual void printData() = 0;
};

class IntItem : public Item
{
private:
    int data;
public:
    explicit IntItem(int d) : data{d} {}
    int getData() { return data; }
    void printData() { std::cout << data; }
};

struct ItemCompare
{
    inline bool operator()(Item* a, Item* b)
    {
        return a->getData() < b->getData();
    }
};

int main()
{
    std::vector<std::unique_ptr<Item>> items{};
    for (auto&& i : { 10, -15, 40, 0, 100 })
    {
        std::unique_ptr<Item> newItem = std::make_unique<IntItem>(i);
        items.push_back(std::move(newItem));
    }
    std::ranges::sort(items, ItemCompare{},
                      [](auto& item_up) { return item_up.get(); });
    for (auto& i : items)
    {
        std::cout << i->getData() << " ";
    }
}

// Outputs
//
//   -15 0 10 40 100

Now, if you want to add some more subclasses to Item, e.g. StringItem, and yet be able to sort a list of generic items, you should provide something to sort on. The example below sorts the items based on the insertion order:

  • Items are assigned an ID in the Item constructor by using a static counter.
  • ItemCompare::operator() compares items by their ID.

[Demo]

#include <algorithm>  // shuffle, sort
#include <iostream>  // cout
#include <memory>  // make_unique, unique_ptr
#include <random>  // default_random_engine, random_device
#include <string>
#include <vector>

class Item
{
private:
    static inline size_t current_id{};
    size_t id;
public:
    Item() : id{current_id++} {}
    size_t getId() { return id; }
    virtual void printData() = 0;
};

class IntItem : public Item
{
private:
    int data;
public:
    explicit IntItem(int d) : Item{}, data{d} {}
    void printData() { std::cout << data; }
};

class StringItem : public Item
{
private:
    std::string data;
public:
    explicit StringItem(const std::string& d) : Item{}, data{d} {}
    void printData() { std::cout << data; }
};

struct ItemCompare
{
    inline bool operator()(Item* a, Item* b) { return a->getId() < b->getId(); }
};

int main()
{
    std::vector<std::unique_ptr<Item>> items{};
    for (auto&& i : { 10, -15, 40 })
    {
        std::unique_ptr<Item> newItem = std::make_unique<IntItem>(i);
        items.push_back(std::move(newItem));
    }
    for (auto&& i : { "blah", "foo", "meh" })
    {
        std::unique_ptr<Item> newItem = std::make_unique<StringItem>(i);
        items.push_back(std::move(newItem));
    }

    std::ranges::shuffle(items, std::default_random_engine{ std::random_device{}()});
    std::cout << "After shuffling... ";
    for (auto& item_up : items) { item_up->printData(); std::cout << " "; }
    std::cout << "\n";

    std::ranges::sort(items, ItemCompare{},
                      [](auto& item_up) { return item_up.get(); });
    std::cout << "After sorting... ";
    for (auto& item_up : items) { item_up->printData(); std::cout << " "; }
    std::cout << "\n";
}

// Outputs:
//
//   After shuffling... -15 foo meh 40 blah 10 
//   After sorting... 10 -15 40 blah foo meh 
rturrado
  • 7,699
  • 6
  • 42
  • 62