For some reason std::map does not find my objects.
Here is my simplified object :
class LangueISO3 {
public:
enum {
SIZE_ISO3 = 3
};
static constexpr char DEF_LANG[SIZE_ISO3] = {'-','-','-'};
constexpr LangueISO3():code() {
for(size_t i(0); i < SIZE_ISO3; i++){
code[i] = DEF_LANG[i];
}
};
LangueISO3(const std::string& s) {strncpy(code, s.c_str(), 3);};
bool operator==(const LangueISO3& lg) const { return strncmp(code, lg.code, 3) == 0;};
bool operator<(const LangueISO3& lg)const { return code < lg.code;};
private:
char code[SIZE_ISO3];
};
My test is :
{
CPPUNIT_ASSERT_EQUAL(LangueISO3("eng"), LangueISO3("eng"));
std::map<LangueISO3, int> lmap;
lmap.emplace(LangueISO3("fra"), 0);
lmap.emplace(LangueISO3("deu"), 1);
lmap.emplace(LangueISO3("eng"), 2);
auto it = lmap.find(LangueISO3("deu"));
CPPUNIT_ASSERT_EQUAL(1, it->second);
}
The first test has no problem, however the second fails. lmap.find() always return lmap.end()
What did I do wrong ?
You can't compare character arrays with the < operator. When you write code < lg.code, the compiler will compare the address of the code array, and not the contents of the arrays.
Change the definition for operator< to use strncmp:
bool operator<(const LangueISO3& lg)const {
return strncmp(code, lg.code, SIZE_ISO3) < 0;
}
Also, the comparison for operator== should use the SIZE_ISO3 constant instead of hardcoding the size at 3.
Related
I'm testing std::set with a custom comparator. But I see the same object getting inserted twice.
Following is the object class:
class Info
{
public:
Info(string n, string oN, int dom):
name(n),
objName(oN),
domain(dom)
{}
void setName(std::string n) { name = n;}
void setObjName(std::string n) { objName = n;}
void setDomain(int dom) { domain = dom; }
std::string getName() const { return name;}
std::string getObjName() const { return objName;}
int getDomain() const { return domain;}
private:
std::string name;
std::string objName;
int domain;
};
Following is my custom comparator:
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if((lhs.getName() < rhs.getName()) || (lhs.getObjName() < rhs.getObjName()) || (lhs.getDomain() < rhs.getDomain()) ){
return true;
}
return false;
}
};
Following is the usage:
Info rst1("rst1", "rstObj1", 1);
Info rst2("rst2", "rstObj2", 2);
Info rst3("rst1", "rstObj3", 3);
std::set<Info,InfoCmp> resetSet;
resetSet.insert(rst1);
resetSet.insert(rst2);
resetSet.insert(rst3);
resetSet.insert(rst1);
resetSet.insert(rst2);
resetSet.insert(rst3);
I see rst2 inserted twice, but it shouldn't be as per my comparator.
I see that you've come up with your own solution, after recognizing from the comments that your original did not impose a strict object ordering as required by set. Here's a different version that only requires operator< and not operator==, making it consistent with the classes and algorithms of the standard library. It also simplifies things if you're e.g. doing a case insensitive comparison.
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if(lhs.getName() < rhs.getName())
return true;
if(rhs.getName() < lhs.getName())
return false;
if(lhs.getObjName() < rhs.getObjName())
return true;
if(rhs.getObjName() < lhs.getObjName())
return false;
return lhs.getDomain() < rhs.getDomain();
}
};
struct InfoCmp2 {
bool operator() (const Info &lhs, const Info &rhs) const {
return std::make_tuple(lhs.getName(), lhs.getObjName(), lhs.getDomain()) < std::make_tuple(rhs.getName(), rhs.getObjName(), rhs.getDomain());
}
};
This operator can written with make_tuple as well, and working fine.
as suggested by Cory Kramer, adding strict ordering in comparator fixed the problem.
struct InfoCmp {
bool operator() (const Info &lhs, const Info &rhs) const {
if((lhs.getName() < rhs.getName())
|| ( (lhs.getName() == rhs.getName()) && (lhs.getObjName() < rhs.getObjName()) )
|| ( (lhs.getName() == rhs.getName()) && (lhs.getObjName() == rhs.getObjName()) && (lhs.getDomain() < rhs.getDomain()) )){
return true;
}
return false;
}
};
struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout&o)const {
return cl < o.cl || cs < o.cs;
}
} ;
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin&o)const {
return mss < o.mss || pinid < o.pinid || tm<o.tm; //no tdi right now it's always empty
}
};
std::map <chainin,chainout> chainmap;
std::map<chainin,chainout>::iterator it;
chainin ci;
chainout co;
string FADEDevicePinInfo::getNetAtPinIdTmTidMss (const LONG p,const string tm, const string tid,const LONG mss){
ci.tm=tm;
// ci.tdi=tid;
ci.tdi="";
ci.mss=(short)mss;
ci.pinid=p;
for (it=chainmap.begin();it!=chainmap.end();it++){
if(it->first.pinid==ci.pinid && it->first.tm==ci.tm&&it->first.mss==ci.mss && it->first.tdi==ci.tdi){
cout << "BDEBUG: found p["; cout<<it->first.pinid; cout<<"] tm["; cout<<it->first.tm.c_str();cout<<"] mss[";cout<<it->first.mss;cout<<"] : ";cout<<it->second.chainSignal.c_str();cout<<endl;
}
}
it=chainmap.find(ci);
if(it == chainmap.end()){
MSG(SEV_T,("no pin data found for pin[%ld]/tm[%s]/tdi[%s]/mss[%ld]",ci.pinid,ci.tm.c_str(),ci.tdi.c_str(),ci.mss));
}
return it->second.cs;
}
This is both printing the successfully found line, and then throwing the sev_t error due to map::find not returning a match. what did i do wrong?
I added print statements thruout the < function, but it seems to be ordering the map correctly, and when i do the lookup, it seems to find the correct mss/pinid, but then only sees one tm, which is the wrong tm.
As noted in comments, you have a bad comparison operator. If you don't know what order the objects should be sorted in, then neither does std::map or any other sorted container.
When you have multiple things to compare, consider deciding which is most important, and use std::tie to compare them, as demonstrated here:
#include <string>
#include <iostream>
struct chainout {
int cl;
std::string cs;
bool operator<(const chainout&o)const {
return std::tie(cl, cs) < std::tie(o.cl, o.cs);
}
};
int main(){
chainout a{ 1, "b" };
chainout b{ 2, "a" };
std::cout << (a < b) << std::endl;
std::cout << (b < a) << std::endl;
}
The operator< for both of your structs are implemented incorrectly.
std::map requires key comparisons to use Strict Weak Ordering. That means when your structs want to compare multiple fields, they need to compare later fields only when earlier fields compare equal. But you are not checking for that condition. You are returning true if any field in one instance compares less-than the corresponding field in the other instance, regardless of the equality (or lack of) in the other fields. So you are breaking SWO, which causes undefined behavior in std::map's lookups.
Try this instead:
struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout &o) const {
/*
if (cl < o.cl) return true;
if (cl == o.cl) return (cs < o.cs);
return false;
*/
return (cl < o.cl) || ((cl == o.cl) && (cs < o.cs));
}
};
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin &o) const {
if (mss < o.mss) return true;
if (mss == o.mss) {
if (pinid < o.pinid) return true;
if (pinid == o.pinid) return (tm < o.tm);
}
return false;
}
};
An easier way to implement this is to use std::tie() instead, which has its own operator< to handle this for you, eg:
struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout &o) const {
return std::tie(cl, cs) < std::tie(o.cl, o.cs);
}
};
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin &o) const {
return std::tie(mss, pinid, tm) < std::tie(o.mss, o.pinid, o.tm);
}
};
Either way, then std::map::find() should work as expected, eg:
std::map<chainin, chainout> chainmap;
string FADEDevicePinInfo::getNetAtPinIdTmTidMss (const LONG p, const string tm, const string tid, const LONG mss)
{
chainin ci;
ci.tm = tm;
//ci.tdi = tid;
ci.tdi = "";
ci.mss = (short) mss;
ci.pinid = p;
std::map<chainin, chainout>::iterator it = chainmap.find(ci);
if (it != chainmap.end()) {
cout << "BDEBUG: found"
<< " p[" << it->first.pinid << "]"
<< " tm[" << it->first.tm << "]"
<< " mss[" << it->first.mss << "]"
<< " : " << it->second.cs
<< endl;
}
}
I'm trying to use std::map with a custom class and in the course of the process the program has to call std::map::find in order to resolve a key to a pair. The custom class doesn't seem to fit well in terms of comparisons.
This is probably better explained in code; I have a class that I want to use as a key:
class index_t
{
int vertex_index;
int normal_index;
int texture_index;
}
std::map<index_t, int> reindexer;
I would like to use
reindexer.find(index_to_find);
In order to find a key with exactly same parameters (exactly same vertex/normal/texture indices) exists in the map already.
So technically I want the std::map::find function to behave like this:
bool find(key_to_find) //this is what I'm expecting from a find function of std::map
{
if(existing_key.vertex == key_to_find.vertex && existing_key.texture == key_to_find.texture && existing_key.normal == key_to_find.normal)
return true;
else return false;
}
However, I'm not sure how to overload the comparison operator appropriately in this situation for it to behave like that (since I can think of no logical less than operator that would suit this class). This is the current operator I'm using:
bool operator<(const index_t& rhv)
{
if(vertex_index < rhv && normal_index < rhv && texture_index < rhv)
return true;
else return false;
}
It doesn't work, since the find relies on the function returning "false" reflexively when comparison orders reversed.
How can I get around this?
This is some more specific, compilable code that reproduces the problem:
class index_t
{
public:
int vertex;
int normal;
int texture;
bool operator< (const index_t& rhv) const
{
if (vertex < rhv.vertex && normal < rhv.normal && texture < rhv.texture)
return true;
else return false;
}
};
map<index_t, int> indexMap;
int main()
{
index_t i;
i.vertex = 0;
i.normal = 0;
i.texture = 0;
index_t i2;
i2.vertex = 1;
i2.normal = 0;
i2.texture = 3;
index_t i4;
i4.vertex = 1;
i4.normal = 0;
i4.texture = 3;
index_t i5;
i5.vertex = 6;
i5.normal = 0;
i5.texture = 3;
index_t i8;
i8.vertex = 7;
i8.normal = 5;
i8.texture = 4;
indexMap.insert(pair<index_t, int>(i, 0));
indexMap.insert(pair<index_t, int > (i2, 1));
if (indexMap.find(i5) != indexMap.end())
cout << "found" << endl;
else
cout << "not found" << endl;
system("pause");
return 0;
}
This results in "found" even though i5 is not a part of the map
I also tried this:
class index_t
{
public:
int vertex;
int normal;
int texture;
};
class index_comparator
{
public:
bool operator()(const index_t& lhv, const index_t& rhv) const
{
if (lhv.vertex == rhv.vertex && lhv.normal == rhv.normal && lhv.texture == rhv.texture)
return true;
else return false;
}
};
map<index_t, int, index_comparator> indexMap;
int main()
{
index_t i;
i.vertex = 0;
i.normal = 0;
i.texture = 0;
index_t i2;
i2.vertex = 1;
i2.normal = 0;
i2.texture = 3;
index_t i4;
i4.vertex = 1;
i4.normal = 0;
i4.texture = 3;
index_t i5;
i5.vertex = 6;
i5.normal = 0;
i5.texture = 3;
index_t i8;
i8.vertex = 7;
i8.normal = 5;
i8.texture = 4;
indexMap.insert(pair<index_t, int>(i, 0));
indexMap.insert(pair<index_t, int > (i2, 1));
if (indexMap.find(i5) != indexMap.end())
cout << "found" << endl;
else
cout << "not found" << endl;
system("pause");
return 0;
}
This also results in "found"
The expected results are that when I call std::map::find on a custom class it compares it other keys in the map and only returns true if an exactly same class (containing the same parameters) exists. Otherwise it should return false.
You have to define a strict order to use class index_t as key in a std::map.
It doesn't need to make sense to you – it just has to provide a unique result of less-than for any pairs of index_t instances (and to grant a < b && b < c => a < c).
The (in question) exposed attempt doesn't seem to fulfil this but the following example would:
bool operator<(const index_t &index1, const index_t &index2)
{
if (index1.vertex != index2.vertex) return index1.vertex < index2.vertex;
if (index1.normal != index2.normal) return index1.normal < index2.normal;
return index1.texture < index2.texture;
}
The simplest way to implement the operator is with tuples, it does all the hard work for you:
bool operator<(const index_t& rhv)
{
return std::tie(vertex_index, normal_index, texture_index) < std::tie(rhv.vertex_index, rhv.normal_index, rhv.texture_index);
}
This is equivalent to the required logic:
bool operator<(const index_t& rhv)
{
if (vertex_index != rhv.vertex_index)
{
return vertex_index < rhv.vertex_index;
}
if (normal_index!= rhv.normal_index)
{
return normal_index< rhv.normal_index;
}
return texture_index< rhv.texture_index;
}
In c++20 this gets even easier with the spaceship operator which does everything for you:
auto operator<=>(const index_t&) const = default;
Your ordering doesn't fulfill the requirements, it has to be what is called a "strict weak ordering relation". It's easiest to not implement that yourself, but instead use existing functionality. Examle:
#include <tuple>
bool operator()(const index_t& lhv, const index_t& rhv) const
{
return std::tie(lhv.vertex, lhv.normal, lhv.texture) <
std::tie(rhv.vertex, rhv.normal, rhv.texture);
}
Your comparison function doesn't have to be logical, it just has to impose a strict weak ordering. Here's a version that works.
bool operator<(const index_t& rhv) const
{
if (vertex < rhv.vertex)
return true;
if (vertex > rhv.vertex)
return false;
if (normal < rhv.normal)
return true;
if (normal > rhv.normal)
return false;
if (texture < rhv.texture)
return true;
if (texture > rhv.texture)
return false;
return false;
}
Since this is not a reasonable operator< for your class it would be better to rename it, to avoid confusion.
struct IndexLT
{
bool operator()(const index_t& lhs, const index_t& rhs)
{
// logic as before
}
};
Then use this newly declared functor like this
std::map<index_t, whatever, IndexLT> my_map;
Yet another alternative would be to use a std::unordered_map since ordering doesn't seem to be significant.
I am trying to add key & value data to my class map member variable - but it does not adds the same -I tried map - insert, [] and emplace method but they are not adding data to map in my loop code - only the value I have inserted during my class construction is available in it - what I need to do to fix the issue - I was expecting that the show method will also print 7, 8,9, 9 -
#include <iostream>
#include <map>
#include <vector>
class A {
public:
A(std::initializer_list <uint32_t> d): data(d) {}
std::vector <uint32_t> data;
bool operator < (const A & rhs) const {
size_t rhsSize = rhs.data.size();
for (size_t i = 0; i < data.size(); i++) {
if (i < rhsSize)
return false;
return true;
}
}
};
class B {
public:
B(const std::map <A, uint32_t> & t): table(t) {}
void Show() {
for (std::map <A, uint32_t> ::iterator it = table.begin(); it != table.end(); ++it) {
for (const auto & i: it->first.data)
std::cout << i << "\n";
std::cout << it->second << "\n";
}
}
std::map <A, uint32_t> table;
};
int main() {
std::map <A, uint32_t> tb = {
{
A {70, 8, 9,10}, 1234}
};
B b(tb);
for (int i = 0; i < 2; i++) {
b.Show();
b.table.emplace(A {7, 8,9, 9}, 1234);
}
return 0;
}
Compile and run the code:
$ c++ -std=c++11 try78.cpp
$ ./a.exe
70
8
9
10
1234
70
8
9
10
1234
Your operator < violates the strict weak ordering requirement that std::map requires of the key. That requires that if comp(a,b) is true then comp(b,a) is false. You use
bool operator < (const A & rhs) const {
size_t rhsSize = rhs.data.size();
for (size_t i = 0; i < data.size(); i++) {
if (i < rhsSize)
return false;
return true;
}
}
to compare the elements and if we compare {70, 8, 9,10} against {7, 8,9, 9} then it returns true and if we flip it around it also returns true. This makes the map think the elements are equal and it won't add the second item.
If you just to make sure that unique vectors are stored in the map then you can use std::vector's operator < in A's operator < like
bool operator < (const A & rhs) const {
return data < rhs.data;
}
and the code will function properly.
I'm trying to find subsequences of vector from a larger vector.
Here's my full code.
#include <iostream>
#include <vector>
using namespace std;
struct Elem {
bool isString;
float f;
string s;
};
void getFounds(vector<Elem> &src, vector<Elem> &dst, vector<size_t> &founds)
{
//what should be in here?
}
int main(int argc, const char * argv[]) {
vector<Elem> elems1 = {{false, 1.f, ""}, {false, 2.f, ""}, {true, 0.f, "foo"},
{false, 1.f, ""}, {false, 2.f, ""}, {true, 0.f, "foo"}}; //the source vector
vector<Elem> elems2 = {{false, 2.f, ""}, {true, 0.f, "foo"}}; //the subsequence to find
vector<size_t> founds; //positions found
getFounds(elems1, elems2, founds);
for (size_t i=0; i<founds.size(); ++i)
cout << founds[i] << endl; // should print 1, 4
return 0;
}
I could do this with std::search if I use it for vector of single types but if I use it for vector of structs, it shows an error saying
"invalid operands to binary expression ('const Elem' and 'const
Elem')"
Is it really impossible to use std::search in this case?
What would be the good way to implement getFounds() in the code?
EDIT : I could make it work by creating an operator function and using std::search
bool operator==(Elem const& a, Elem const& b)
{
return a.isString == b.isString && a.f == b.f && a.s == b.s;
}
void getFounds(vector<Elem> &src, vector<Elem> &dst, vector<size_t> &founds)
{
for (size_t i=0; i<src.size(); ++i) {
auto it = search(src.begin()+i, src.end(), dst.begin(), dst.end());
if (it != src.end()) {
size_t pos = distance(src.begin(), it);
founds.push_back(pos);
i += pos;
}
}
}
However, I would appreciate if anyone can give me an advice to make the code simpler.
Is it really impossible to use std::search in this case?
No, you just need to implement the operator== function in your struct as you've done. You could also implement the operator!= as well, example:
struct Elem
{
bool isString;
float f;
std::string s;
bool operator==(const Elem& other) const {
return (this->isString == other.isString &&
this->f == other.f &&
this->s == other.s);
}
bool operator!=(const Elem& other) const {
return !(*this == other);
}
};
What would be the good way to implement getFounds() in the code? ... advice to make it simpler.
Simpler is relative, especially since you're already using the standard library to achieve what you want; however, you could also implement the getFounds function like so:
void getFounds(const std::vector<Elem>& src, const std::vector<Elem>& sub, std::vector<size_t>& founds)
{
size_t count = 0, tot = 0;
auto beg = sub.begin();
for (auto look = src.begin(); look != src.end();) {
if (*look != *beg) { ++look; ++count; continue; }
for (tot = 0; beg != sub.end(); ++beg, ++look, ++tot) {
if (look == src.end()) { break; }
if (*look != *beg) { break; }
}
if (tot == sub.size()) { founds.push_back(count); }
count += tot;
beg = sub.begin();
}
}
I don't know if that's "simpler" for your needs, as it does essentially what the std::search algorithm would do (loop and check and break if elements aren't matching, etc.), it's just "another" way to do it.
Hope that helps.
Override == and iterate through both arrays looking for match:
bool operator==(Elem const &el1, Elem const &el2)
{
return
el1.isString == el2.isString
&&
el1.f == el2.f
&&
el1.s == el2.s;
}
void getFounds(std::vector<Elem> const &src, std::vector<Elem> const &dst, std::vector<size_t> &founds)
{
for (size_t i = 0; i < src.size(); ++i)
for (size_t j = 0; j < dst.size(); ++j)
if (src[i] == dst[j])
founds.push_back(i);
}
This however will find every index. For example your example will print 1 2 4 5. If you want to abort after 1st find, you need to add some additional logic to it.