How to Create a Std::Set of Structures

How can I create a std::set of structures?

The std::set template provides an associative container that contains a sorted set of unique objects. The key words there is sorted and unique. To support sorting, a number of possibilities ensue, but ultimately the all must lead to a conforming with strict weak ordering.

The second template argument to std::set is a comparison type. The default, std::less<Key>, is supplied by the standard library, where Key is the type of object you're storing in your container (in your case, Point). That default simply generates a comparison using any allowable available operator < supporting the key type. Which means one way or another, if you're using the default comparator (std::less<Point> in your case), then your class must suppose operations like this:

Point pt1(args);
Point pt2(args);

if (pt1 < pt2) // <<=== this operation
dosomething();

Multiple methods for doing this appear below:

Provide a member operator <

By far the easiest method to accomplish this is to provide a member operator < for your Point class. In doing so pt1 < pt2 becomes valid and std::less<Point> is then happy. Assuming your class is a traditional x,y point, it would look like this:

struct Point
{
int x,y;

// compare for order.
bool operator <(const Point& pt) const
{
return (x < pt.x) || ((!(pt.x < x)) && (y < pt.y));
}
};

Provide a Custom Comparator Type

Another method would be to provide a custom comparator type rather than relying on std::less<Point>. The biggest advantage in this is the ability to define several that can mean different things, and use them in containers or algorithms as appropriately needed.

struct CmpPoint
{
bool operator()(const Point& lhs, const Point& rhs) const
{
return (lhs.x < rhs.x) || ((!(rhs.x < lhs.x)) && (lhs.y < rhs.y));
}
};

With that, you can now declare your std::set like this:

std::set<Point,CmpPoint> mySet;

Something to consider with this approach: The type is not part of Point, so any access to private member variables or functions has to be accounted for via friending in come capacity.


Provide a free-function operator <

Another less common mechanism is simply provide a global free-function that provides operator <. This is NOT a member function. In doing this, once again, the default std::less<Point> will result in valid code.

bool operator <(const Point& lhs, const Point& rhs)
{
return (lhs.x < rhs.x) || ((!(rhs.x < lhs.x)) && (lhs.y < rhs.y));
}

This may seem a mix of both the custom comparator and the member operator, and indeed many of the pros and cons of each come along. Ex: like the member operator <, you can just use the default std::less<Point>. Like the custom comparator, this is a non-class function, so access to private members must be provided via friending or accessors.


Summary

For your needs, I'd go with the simple approach; just make a member operator <. Chances are you'll always want to order your Points in that fashion. If not, go with the custom comparator. In either case make sure you honor strict weak ordering.

How to have a set of structs in C++

This might help:

struct foo
{
int key;
};

inline bool operator<(const foo& lhs, const foo& rhs)
{
return lhs.key < rhs.key;
}

If you are using namespaces, it is a good practice to declare the operator<() function in the same namespace.


For the sake of completeness after your edit, and as other have pointed out, you are trying to add a foo* where a foo is expected.

If you really want to deal with pointers, you may wrap the foo* into a smart pointer class (auto_ptr, shared_ptr, ...).

But note that in both case, you loose the benefit of the overloaded operator< which operates on foo, not on foo*.

std::set struct sorted by property value in struct c++

One option is to overload operator< for your structure. Any standard algorithm/container that wants to compare their sort order will use that by default.

bool operator<(abc const & a, abc const & b) {
// your code here
}

Alternatively, you can specify your comparator just for the set:

std::set<abc, bool(*)(abc,abc)> my_set(comp);

This would be a bit more convenient with a function class rather than a function:

struct comp {
bool operator()(abc const & a, abc const & b) {
// your code here
}
};

std::set<abc, comp> my_set;

Add struct in std::list

To use std::find_if and a lambda:

std::list<data>::iterator it = std::find_if(list.begin(), list.end(),
[&data1](const data& rhs) {
return
data1.str == rhs.str &&
data1.num == rhs.num &&
data1.num2 == rhs.num2 &&
data1.str2 == rhs.str2;
}
);

In this case I would however recommend defining data::operator== and using std::find instead:

struct data {
std::string str;
int num;
int num2;
std::string str2;

bool operator==(const data& rhs) const {
return
str == rhs.str &&
num == rhs.num &&
num2 == rhs.num2 &&
str2 == rhs.str2;
}
};

std::list<data>::iterator it = std::find(list.begin(), list.end(), data1);

in C++20, you can simplify this by defaulting operator<=> (the spaceship operator):

a defaulted <=> overload will also allow the type to be compared with <, <=, >, and >=. If operator<=> is defaulted and operator== is not declared at all, then operator== is implicitly defaulted.

Given that, here's how to allow data to be compared using all those operators:

struct data {
std::string str;
int num;
int num2;
std::string str2;

friend auto operator<=>(const data&, const data&) = default;
};

Use a set of structs and avoid duplicate structs in a set

std::set<T*> will create a set of memory locations, not a set of T values.

If you want to compare the pointed objects, you need to provide a custom comparator:

struct Ptr_compare {
template<typename T>
constexpr bool operator()( const T* lhs, const T* rhs ) const {
return *lhs < *rhs;
}
};

// This is a struct for graph
struct Graph {
set<Node*, Ptr_compare> Nodes;
set<Edge*, Ptr_compare> Edges;
map<int, Node*> nodeMap;
};

However:

Be aware that the code I wrote answers your question, but is still not correct for your use-case, it's only ok to use this for non-owning pointers, which is most definitely not your case.

This is not a problem with my solution per-se, but a fundamental issue in what you are trying to accomplish. Something needs to call delete on the dedupped objects.



Related Topics



Leave a reply



Submit