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 Point
s 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>=
. Ifoperator<=>
is defaulted andoperator==
is not declared at all, thenoperator==
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
What Is the Purpose of a Declaration Like Int (X); or Int (X) = 10;
How to Make an Image Resize to Scale in Qt
Erasing Vector::End from Vector
Conversion from Int** to Const Int**
What Is Aggregate Initialization
Covariant Return Type and Type Conversion
Why Use #Ifndef Class_H and #Define Class_H in .H File But Not in .Cpp
Need a Fast Random Generator for C++
How to Get a Non-Const C String Back from a C++ String
How to Avoid Errors While Using Crtp
What Is the Purpose of the _Chkstk() Function
Does Reinterpret_Cast Lead to Undefined Behavior
How to Force Inclusion of "Unused" Object Definitions in a Library
Difference Between Regex_Match and Regex_Search
Coding Practices Which Enable the Compiler/Optimizer to Make a Faster Program