How can I insert custom objects into a std::map? - c++

I've read through the map::map reference at cplusplus.com and I'm still not sure how to get this to work. All I want to do is create a map like the following:
std::map<TriSpec, unsigned int> TriSpecMap;
Then I want to insert into it as follows:
result = TriSpecMap.insert(std::make_pair(triSpecObject, anUnsignedInt));
The following is a short example of my TriSpec header and .cpp:
//TriSpec.h
#ifndef TRISPEC_H
#define TRISPEC_H
class TriSpec
{
public:
TriSpec(void);
~TriSpec(void);
unsigned int m_position;
};
bool operator< (const TriSpec& lhs, const TriSpec& rhs);
#endif
//TriSpec.cpp
#include "TriSpec.h"
TriSpec::TriSpec(void){}
TriSpec::~TriSpec(void){}
bool operator< (const TriSpec& lhs, const TriSpec& rhs)
{
if (lhs.m_position < rhs.m_position) return true;
else return false;
}
Am I overloading the correct operator? Is the function/formatting I'm using correct? When I look at result.second, it is always true, even when I know the object being inserted should already exist in the map.

I do not see any problem with your code except that bool operator should be friend method (it does not even compile without friend keyword):
bool friend operator< (const TriSpec& lhs, const TriSpec& rhs)
{
return (lhs.m_position < rhs.m_position);
}
Then it works as expected:
int main(int argc, _TCHAR* argv[])
{
std::map<TriSpec, unsigned int> TriSpecMap;
TriSpec triSpecObject1;
triSpecObject1.m_position = 1;
TriSpec triSpecObject2;
triSpecObject2.m_position = 1;
TriSpec triSpecObject3;
triSpecObject3.m_position = 3;
std::pair<std::map<TriSpec, unsigned int>::iterator, bool> retVal =
TriSpecMap.insert(std::make_pair(triSpecObject1, 1));
retVal = TriSpecMap.insert(std::make_pair(triSpecObject2, 1));
retVal = TriSpecMap.insert(std::make_pair(triSpecObject3, 1));
return 0;
}
The result of first insertion is true, the result of second is false a the third is true again - as it should be. The map container contains two objects then - triSpecObject1 a triSpecObject3.

That doesn't look like it would compile - your operator< doesn't return a value in all cases. (Edit: you've fixed this, thanks.) You can simplify it quite a bit:
bool operator< (const TriSpec& lhs, const TriSpec& rhs)
{
return (lhs.m_position < rhs.m_position);
}
Since you don't show the complete code that inserts into the map, it's impossible to say why the return value .second always returns true.

Since C++11 you can also use a lambda expression instead of providing an operator< for your class. As a result, you can create your map with only two lines of code as follows:
int main()
{
auto comp = [](const TriSpec& t1, const TriSpec& t2) { return t1.m_position < t2.m_position; };
std::map<TriSpec, unsigned int, decltype(comp)> TriSpecMap(comp);
TriSpec t1, t2, t3;
t1.m_position = 1;
t2.m_position = 3;
t3.m_position = 5;
auto retVal = TriSpecMap.emplace(t1, 4);
retVal = TriSpecMap.emplace(t2, 2);
retVal = TriSpecMap.emplace(t3, 6);
for (auto const &kv : TriSpecMap)
std::cout << kv.first.m_position << ": " << kv.second << std::endl;
return 0
}
Output:
1: 4
3: 2
5: 6
Code on Ideone

Related

std::set custom comparator for non null strings

As the title suggests am trying to implement comparison functor for my defined structure. Here is the sample snippet
#include<set>
struct testData
{
char * data;
int size;
};
class compare
{
public:
bool operator()(const testData & lhs,
const testData & rhs) const noexcept
{
return memcmp(lhs.data, rhs.data, lhs.size<rhs.size?lhs.size:rhs.size) < 0;
}
};
int main()
{
std::set<testData,compare>S;
....
return 0;
}
Issue in the comparison function is since am taking lesser size,this
case fails
suppose there is already this data present {"test",4},and i am trying
to find {"test1",5}.it will say as matched.How can i modify comparison
to overcome this?
Update:
changed to this
class compare
{
public:
bool operator()(const testData & lhs,
const testData & rhs) const noexcept
{
if (lhs.size == rhs.size)
return memcmp(lhs.data, rhs.data, lhs.size) < 0;
return lhs.size < rhs.size;
}
};
will this work?
You'd use std::lexicographical_compare.
Lexicographical comparison is a operation with the following properties:
Two ranges are compared element by element.
The first mismatching element defines which range is lexicographically less or greater than the other.
If one range is a prefix of another, the shorter range is lexicographically less than the other.
If two ranges have equivalent elements and are of the same length, then the ranges are lexicographically equal.
An empty range is lexicographically less than any non-empty range.
Two empty ranges are lexicographically equal.
class compare
{
public:
bool operator()(const testData & lhs,
const testData & rhs) const noexcept
{
return std::lexicographical_compare(lhs.data, lhs.data + lhs.size, rhs.data, rhs.data + rhs.size);
}
};
the reason it failes is because you compare only the length of the smaller string.
In your example only the "test" part of "test1" will be compared.
You can try this:
bool operator()(const testData & lhs,
const testData & rhs) const noexcept
{
int ret = memcmp(lhs.data, rhs.data, lhs.size<rhs.size?lhs.size:rhs.size);
return ret == 0 ? lhs.size < rhs.size : ret < 0 ;
}

Operator() crashing program on MSVC C++17 (2019)

The following code doesn't work with MSVC++ 2019, but it works on GCC compiler.
#include <set>
#include <string>
#include <iostream>
struct MyData {
MyData() {}
MyData(std::string keyA, std::string keyB) :keyA(keyA), keyB(keyB) {}
std::string keyA;
std::string keyB;
};
struct Compare {
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA < rhs.keyA)
return true;
if (lhs.keyB < rhs.keyB)
return true;
else
// All else conditions would be false
return false;
}
};
int main()
{
std::set<MyData, Compare> s;
s.insert(MyData("Clark", "Alice"));
s.insert(MyData("Bob", "Alice"));
s.insert(MyData("Alice", "Bob"));
s.insert(MyData("Derek", "Clark"));
for (auto& i : s)
{
std::cout << i.keyA << ", " << i.keyB << std::endl;
}
}
Here's the error on MSVC:
While on GCC, it shows this output:
Alice, Bob
Bob, Alice
Clark, Alice
Derek, Clark
Process returned 0 (0x0) execution time : 0.971 s
Press any key to continue.
What's causing this error, and how to define the operator correctly?
Compare::operator() is not correct. It does not meet the criteria of strict weak ordering.
You can change it to:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA != rhs.keyA)
return (lhs.keyA < rhs.keyA)
return (lhs.keyB < rhs.keyB);
}
It can be further simplified using std::tie.
bool operator() (const MyData& lhs, const MyData& rhs) const
{
return std::tie(lhs.keyA, lhs.keyB) < std::tie(rhs.keyA, rhs.keyB);
}
Your comparator fails to adhere to the rules for strict weak ordering, that is to say it is possible to have the following situation:
a < b
b < c
a >= c
MSVC has evidently detected this and raised an assertion failure.
To fix this, you can change your comparator as follows:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA < rhs.keyA)
return true;
if (lhs.keyA == rhs.keyA && lhs.keyB < rhs.keyB)
return true;
return false;
}
Or, more compactly, use std::tie:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
return std::tie (lhs.keyA, lhs.keyB) < std::tie (rhs.keyA, rhs.keyB);
}
I also got rid of the redundant else.

How to ensure no duplicates in boost::unordered_set of custom data

I need my container to contain unique elements only, so I have a structure like this:
class OD
{
private:
std::string key;
public:
OD(){}
OD(const WayPoint &origin, const WayPoint &destination):
origin(origin), destination(destination)
{
std::stringstream str("");
str << origin.node_->getID() << "," << destination.node_->getID();
key = str.str();
}
bool operator<(const OD & rhs) const
{
return key < rhs.key;
}
bool operator()(const OD & rhs, const OD & lhs)
{
return rhs < lhs;
}
};
and a container :
std::set<OD,OD> t;
now I need to change my container to boost::unordered_set type, do I need to modify the functor? I am confused because I know I can't separate order and uniqueness implementation and this time the container is not ordered . So I fear my operator() overload would be useless.
Here's an example of defining custom hash and comparison operators for an unordered_set:
#include <iostream>
#include <functional>
#include <unordered_set>
struct X
{
std::string key_;
};
int main() {
std::unordered_set<X,
std::function<size_t(const X&)>,
std::function<bool(const X&, const X&)> > s{
5, // initial bucket count
[](const X& x) { return std::hash<decltype(x.key_)>()(x.key_); },
[](const X& lhs, const X& rhs) { return lhs.key_ == rhs.key_; }
};
s.insert({"one"});
s.insert({"two"});
s.insert({"three"});
for (auto& x : s)
std::cout << x.key_ << '\n';
}
See it run here.

Multiple Overloading of Operators

as you can see from the code I want to overload the < operator twice. 1 to sort by dist and the other by nodeID. I would like to check if there is any way to call the different overloaded methods. For example in the compLoc method, when I use the sort() method I want it to be sorted by nodeID but in other methods I want it to be sorted by dist.
struct AttSet{
int nodeID;
double dist;
bool operator < (const AttSet & str) const{
return (dist < str.dist);
}
/*
bool operator <(const AttSet & str){
return (nodeID < str.nodeID);
*/
bool operator == (const AttSet & str){
return nodeID == str.nodeID;
}};
void compLoc(Edge *edge, vector<Node*> &vertices){
int l = edge->length;
int vl = edge->head->nodeID;
int vr = edge->tail->nodeID;
/*
sort(vertices[vl]->attSet.begin(), vertices[vl]->attSet.end());
sort(vertices[vr]->attSet.begin(), vertices[vr]->attSet.end());
vector<AttSet> vInterSec;
set_intersection(vertices[vl]->attSet.begin(), vertices[vl]->attSet.end(), vertices[vr]->attSet.begin(), vertices[vr]->attSet.end(), back_inserter(vInterSec));
*/}
You cannot have overloads that have the same signature. This holds for any function. How would you try to decide which version to use?
If you want sort the object based on different criteria you should use the sort version that takes a custom comparer function as the third argument.
Edit:
Of course you need to provide the comparer. I would suggest providing the comparers as static functions of the class if you have such power. This way you will not pollute enclosing namespace and you can access privates of the class with out exposing any getters. Since your properties are public the lambda would suffice, and probably be the best/cleanest approach.
Feeling adventurous I made a simple c++11 exercise program. For what it's worth, if you ever decided to go for proper encapsulation, I've shown both approaches:
#include <iostream>
#include <algorithm>
#include <vector>
#include <initializer_list>
#include <cassert>
using namespace std;
template<typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& v){
for(const auto& el : v){
out << el << '\n';
}
return out;
}
class A {
int a;
int b;
public:
A(std::initializer_list<int> l){
assert(l.size() == 2);
auto i = l.begin();
a = *i;
++i;
b = *i;
}
friend std::ostream& operator<<(std::ostream& stream, const A& e){
return stream << e.a << ' ' << e.b;
}
static bool compareViaA(const A& lhs, const A& rhs){
return rhs.a > lhs.a;
}
static bool compareViaB(const A& lhs, const A& rhs){
return rhs.b > lhs.b;
}
};
int main() {
std::vector<A> v {{2,3}, {3,2}, {1,4}, {4,1}};
//sort(v.begin(), v.end(), [](const A& a, const A& b){return a.a > b.a;}) // fails because of privacy violation
sort(v.begin(), v.end(), A::compareViaA);
std::cout << v << '\n';
sort(v.begin(), v.end(), A::compareViaB);
std::cout << v << '\n';
return 0;
}
Live: http://ideone.com/lDMujx.
I think you can implement this by using functor and take the comparator(operator< overload) outside the AttSet.
Here is a simple example:
struct AtrComparator {
bool distcmp;
AttrComparator(bool distcmp): distcmp(distcmp) {}
bool operator() (const AttSet &s1, const AttSet &s2) {
if(distcmp) {
return s1.dist < s2.dist;
} else {
return s1.nodeID < s2.nodeID;
}
}
}
And then you can do the sort through different feed, dist or nodeID.
.e.g:
sort(vertices[vl]->attSet.begin(), vertices[vl]->attSet.end(), AttComparator(true));
sort(vertices[vl]->attSet.begin(), vertices[vl]->attSet.end(), AttComparator(false));
You can't do that. They have the same signature exactly.
Use a functor or a lambda and pass it to whatever algorithm you want.
std::sort(std::begin(container), std::end(container),
[](const element_type& lhs, const element_type& rhs) { return ...; });
Another way to do this:
struct compare_by_node_id {
bool operator()(const AttSet& lhs, const AttSet& rhs) const {
return lhs.nodeID < rhs.nodeID;
}
};
struct compare_by_dist {
bool operator()(const AttSet& lhs, const AttSet& rhs) const {
return lhs.dist < rhs.dist;
}
};
And you could pass that to the algorithm like:
std::sort(std::begin(container), std::end(container), compare_by_node_id());
you cannot do that because compiler doesn't see difference between:
bool operator < (const AttSet & str) const; //this const doesn't allow to override any property of object(instance of AttSet) if I remember
and
bool operator < (const AttSet & str);
there're the same same return type, same parameter (same signature)
compiler cannot choose which one is better
There's not a great way to do this as far as I am aware, since the compiler will see these as the exact same and will throw an error. If you need to do this, use the < operator as whatever will occur the most often, and then write a method that you can call to compare two object. Something like this:
bool operator< (const Blah &blah) const {
return (most often operation)
}
bool Blah::other_operation(const Blah &blah) const {
return (other operation)
}

How use std::multiset with multiple comparator function?

Good afternoon, I have a C++ class Range which implements a operator < for use by std::multiset<Range> ranges_type.
Since the multiset constructor don't specify a a custom comparator functor, it uses the std::less operator <.
However, I need to use a second comparator functor for std::multiset ranges_type. Specifically, I would specify a second comparator:
std::multiset<Range, PointerCompare> where struct PointerCompare looks this :
struct PointerCompare{
bool operator()(const Range& a, const Range& b) const {
return (a.mPtr == b.mPtr)
}
Is it possible to use std:multiset with multiple comparator functions or is there a workaround? Thank you
The class Range looks this:
class Range {
public:
explicit Range(int item){
mLow = item;
mHigh = item;
mPtr = 0;
}
Range(int low, int high, char* ptr = 0,char* mapptr = 0){
mLow = low;
mHigh = high;
mPtr = ptr;
}
Range(void){
mLow = 0;
mHigh = 0;
mPtr = 0;
}
Range(const Range& r):
mLow(r.mLow),
mHigh(r.mHigh),
mPtr(r.mPtr)
{
}
bool operator==(const Range& rhs) const{
return (mLow <= rhs.mLow && mHigh >= rhs.mHigh);
}
bool operator<(const Range& rhs) const{
return mHigh < rhs.mHigh;
}
int low() const { return mLow; }
int high() const { return mHigh; }
char* getPtr() const { return mPtr; }
private:
int mLow;
int mHigh;
char* mPtr;
}; // class Range
Sounds almost like you'd be better if you used something from Boost::MultiIndex rather than trying to force several different comparator functions onto a std::multiset. They have a bunch of different container types (see here.) In particular I'd look at the ordered_indices versions.
I may have found a workaround for multiple comparator functions: Here it is:
Range targetRange = Range(PreviousNCopy,PreviousN, TmpPrevMapPtr);
bool Found = std::binary_search( ranges_type.begin(), ranges_type.end(),
targetRange, MyComparator() );
where: MyComparator is a struct :
struct MyComparator {
bool operator () ( const Range& d1, const Range& d2 ) const
{
return d1.getPtr() < d2.getPtr();
}
};
std::binary_search take o(log n) time but the std::multiset ranges_type must always remain sorted. Thank you.