How can I make find() work with a set of structs? - c++

I am using a set to hold structs which contain several strings. I want to be able to use the find() functionality of sets. However, since the set is holding structs, it doesn't work. I want find() to look only at one of the strings in the struct. How can this be done?
Here's the code that I tried to use. It works fine except for the part where find() is used:
#include <iostream>
#include <string>
#include <set>
using namespace std;
struct test
{
string key;
string data;
};
bool operator<(const test & l, const test & r)
{
return l.key < r.key;
}
bool operator==(const test & l, const test & r)
{
return l.key == r.key;
}
set<test> s;
int main()
{
test newmember;
newmember.key = "key";
newmember.data = "data";
s.insert(newmember);
s.find("key");
}
Here are the error messages that I get when I try to compile it:
test.cpp:30:7: error: no matching member function for call to 'find'
s.find("key");
~~^~~~
In file included from test.cpp:3:
In file included from /usr/include/c++/4.2.1/set:65:
/usr/include/c++/4.2.1/bits/stl_set.h:429:7: note: candidate function not viable: no known conversion from 'const char [4]' to 'const key_type' (aka 'const test') for 1st argument
find(const key_type& __x)
^
/usr/include/c++/4.2.1/bits/stl_set.h:433:7: note: candidate function not viable: no known conversion from 'const char [4]' to 'const key_type' (aka 'const test') for 1st argument
find(const key_type& __x) const
^
1 error generated.

I suggest you operator< and operator== to your struct instead of overloading the global operator, I find it much cleaner; example:
struct test
{
string key;
string data;
bool operator<(const test& rhs) const
{
return key < rhs.key;
}
bool operator==(const test& rhs) const
{
return key == rhs.key;
}
};
Now on to your real problem - your are passing a string to the find() function, but it only accepts structs of type test. In order to do so, add a constructor for automatic conversion, so the final struct would look like this:
struct test
{
string key;
string data;
test(const std::string& strKey = "", const std::string& strData = "")
: key(strKey),
data(strData) {}
bool operator<(const test& rhs) const
{
return key < rhs.key;
}
bool operator==(const test& rhs) const
{
return key == rhs.key;
}
};
Then passing a string to find() would automatically call the constructor and create a temporary test struct containing only the relevant key. Note that in this special case, the constructor must not be declared explicit.

To be able to put your structs into set you have to specify operator< for your struct. You can make the operator< return result from comparing corresponding string members.
To be able to use find you can specify operator== for your struct to return true if corresponding string members are equal.
Sample:
// code from your question used here
int main()
{
test newmember;
newmember.key = "key";
newmember.data = "data";
test findMember;
findMember.key = "key";
// as operator== and operator< doesn't care about data field we can left it be
// initialized by default constructor
s.insert(newmember);
s.find(findMember);
}
If you want to call find() with string parameter you can provide an implicit constructor from string for your test struct for example like this:
struct test {
//...
test(const string &in_key) : key(in_key) {}
//...
};
But usage of implicit constructors isn't a good technique, because it can lead to some unpredictable conversions somewhere further in your code.

First and foremost, in order to make std::set::find() work with your struct, you don't need to specify operator==, as explained for std::set:
[...] two objects a and b are considered equivalent if neither compares less than the other: !comp(a, b) && !comp(b, a).
However, your actual problem is, that you can't search for parts of your struct by using find() prior to C++14. Prior to C++14, find() expects an element as argument, whose type matches the type of elements stored in the set. For your case, this means that you have to provide an instance of test. However, as your operator< compares only the key variables, you can use a dummy value for the data variable, for example, as follows:
test newmember;
newmember.key = "key";
newmember.data = "data";
s.insert(newmember);
auto it = s.find(test{ "key", "" }); // "" is the dummy variable.
std::cout << it->key << ", " << it->data << std::endl;
Output:
key, data
Please be aware that your operator< which compares only the key variables has a side effect: Two instances of test can only be stored in the set if their key variables are different, even if their data variables are different. For example, if you append the following code to the code above, then newmember will not be inserted (again):
newmember.data = "otherdata";
s.insert(newmember); // Insertion fails!
for (auto const& t : s) {
std::cout << t.key << ", " << t.data << std::endl;
}
Output:
key, data
Consequently, if you want to store multiple elements with the same key, then you might have to choose a different container.
Anyway, if a set of struct is fine for you and you can make use of C++11, then you can also use a lambda expression
instead of defining operator< for your struct:
auto comp = [](const test& t1, const test& t2) { return t1.key < t2.key; };
std::set<test, decltype(comp)> s(comp);
Code on Ideone
C++14
Since C++14, you can use find() to do a "transparent comparison" as explained on std::set::find().
This means, that you can find an element that compares equivalent to the given argument.
In detail this means, that you have to define operator< and your set as follows:
bool operator<(const test& t, const std::string& str) { return t.key < str; }
bool operator<(const std::string& str, const test& t) { return str < t.key; }
bool operator<(const test& t1, const test& t2) { return t1.key < t2.key; }
std::set<test, std::less<>> s;
Then you can perform your find() operation as you would have expected it to work:
test newmember;
newmember.key = "key";
newmember.data = "data";
s.insert(newmember);
auto it = s.find("key");
std::cout << it->key << ", " << it->data << std::endl;
Output:
key, data
Code on Ideone

Only you have to override operator<() function to enhance find function.
In your case simply replace operator<() function as..
bool operator<(const offer& t1) const {
if(this->key< t1.key)
return true;
else if(this->key == t1.key){
if(this->data < t1.data)
return true;
else
return false;
}
else
return false;
}

Related

Generic comparison operator for structs

In many of my unit tests I need to compare the contents of simple structs having only data members:
struct Object {
int start;
int stop;
std::string message;
}
Now, if I want to write something like:
CHECK(object1==object2);
I always have to implement:
bool operator==(const Object& lhs, const Object& rhs) {
return lhs.start==rhs.start && lhs.stop==rhs.stop && lhs.message=rhs.message;
}
Writing all these comparison functions becomes tedious, but is also prone to errors. Just imagine, what will happen if I add a new data member to Object, but the comparison operator will not be updated.
Then I remembered my knowledge in Haskell and the magic deriving(Eq) directive, which just generates a sane comparison function for free.
How, could I derive something similar in C++?
Happily, I figured out that C++17 comes with a generic operator== and that every struct should be easily convertible to an std::tuple by the virtue of std::make_tuple.
So I boldly tried the following:
#include <tuple>
#include <iostream>
#include <tuple>
template<typename T>
bool operator==(const T& lhs, const T& rhs)
{
auto leftTuple = std::make_tuple(lhs);
auto rightTuple = std::make_tuple(rhs);
return leftTuple==rightTuple;
}
struct Object
{
std::string s;
int i;
double d;
};
int main(int arg, char** args)
{
std::cout << (Object{ "H",1,2. } == Object{ "H",1,2. }) << std::endl;
std::cout << (Object{ "A",2,3. } == Object{ "H",1,2. }) << std::endl;
return EXIT_SUCCESS;
}
But, unfortunately it just doesn't compile and I really don't know why. Clang tells me:
main.cpp:11:18: error: use of overloaded operator '==' is ambiguous (with operand types
'std::tuple<Object>' and 'std::tuple<Object>')
return leftTuple==rightTuple;
Can I possibly fix this compile error to get my desired behavior?
No, since comparing tuples reverts to comparing the elements of the tuple, so leftTuple == rightTuple tries to compare two Objects which is not possible.
that every struct should be easily convertible to an std::tuple by the virtue of std::make_tuple
No, you'll just get a tuple with one element, the struct.
The trick is to use std::tie:
std::tie(lhs.mem1, lhs.mem2) == std::tie(rhs.mem1, rhs.mem2)
but that has the same problem as your original solution. Unfortunately C++17 doesn't have any facility to avoid this problemyou could write a macro :). But in C++20 you will be able to do:
struct Object
{
std::string s;
int i;
double d;
bool operator==(const Object &) const = default;
};
which will generate the correct comparison operators for Object.

Sorting a list of mixed data structures on the basis of integer value and finding an element in it based on the string value in c++?

I'm programming in QtCreator and currently I'm using QList as shown in the following code:
#include <vector>
#include <map>
#include <algorithm>
#include <QDebug>
class mixed
{
public:
int number;
QString name;
QString address;
mixed(int n, QString s, QString a)
{
number = n;
name = s;
address = a;
}
};
bool myfunction (mixed i,mixed j) { return (i.number<j.number); }
bool mySearch (mixed i, mixed j) {
return (i.name==j.name);
}
int main()
{
QList<mixed>myV;
mixed object(100, "akkas", "100");
myV.push_back(object);
myV.push_back(mixed(2, "akkas1", "2"));
myV.push_back(mixed(1111, "akkas2", "1111"));
myV.push_back(mixed(-1, "akkas3", "-1"));
myV.push_back(mixed(7, "akkas4", "7"));
myV.push_back(mixed(0, "akkas0", "0"));
myV.push_back(mixed(2, "akkas0", "21"));
for(int i=0; i<myV.size(); i++)
{
qDebug()<<myV.at(i).number<<" "<<myV.at(i).name<<" "<<myV.at(i).address<<endl;
}
std::sort (myV.begin(), myV.end(), myfunction);
for(int i=0; i<myV.size(); i++)
{
qDebug()<<myV.at(i).number<<" "<<myV.at(i).name<<" "<<myV.at(i).address<<endl;
}
// QList<mixed>::iterator it;
// it = std::search(myV.begin(), myV.end, object, mySearch);
// if (it!=myV.end())
// qDebug() << "found at position " << (it-myV.begin()) << '\n';
// else
// qDebug() << "not found\n";
// qDebug()<<myV.indexOf(object)<<endl;
return 0;
}
But the problem is the commented out line
qDebug()<<myV.indexOf(object)<<endl;
fails because
no match for 'operator==' (operand types are 'mixed' and 'const mixed')
if (n->t() == t)
^
On the other hand I was trying to use std::search using predicate comparison where predicate is as below:
bool mySearch (mixed i, mixed j) {
return (i.name==j.name);
}
But I can't understand why it gives the error
no matching function for call to 'search(std::vector<mixed>::iterator, <unresolved overloaded function type>, mixed&, bool (&)(mixed, mixed))'
it = std::search(myV.begin(), myV.end, object, mySearch);
^
I need to use something that can allow me easy sorting using the integer value of the mixed data type and finding any element using the string value of that type.
What mistake am I doing in my approach? What alternative do I have? Could you please give me some example with code?
Thanks.
EDIT
After the responses I have corrected end to end(), but even then there are errors. Now the error is:
error: no matching function for call to 'search(QList<mixed>::iterator, QList<mixed>::iterator, mixed&, bool (&)(mixed, mixed))'
std::search(myV.begin(), myV.end(), object, mySearch);
^
qDebug()<<myV.indexOf(object)<<endl;
For this to work you need to implement == operator for class mixed. Example:
bool operator==(const mixed& rhs)
{
return name == rhs.name;
}
For second error, As rightly pointed out in the comments, you need to correct end iterator.
std::search(myV.begin(), myV.end(), object, mySearch);
^^^^^^^^
UPDATE: However this is NOT correct use of std::search. Use std::search which relies on == operator.
std::find(myV.begin(), myV.end(), object); // Uses == operator to find match
Reference: http://www.cplusplus.com/reference/algorithm/find/
This should work.
For cases like this, where your find criterion may differ from call to call, I prefer find_if: it does not force me to overload operator== for my type.
auto it = std::find_if(begin(myVec), end(myVec),
[](const auto &a, const auto &b) { return a.name == b.name; });
auto index = std::distance(begin(myVec), it);

Is it possible to use std::sort with a sort function that takes extra arguments?

This is something that I've been considering for a while. I've done some research and can't find anything on it, but I haven't found anything to the contrary either.
Consider the std::sort function in <algorithm>. It takes two iterators and a function pointer as arguments. So if I wanted to sort a vector of strings alphabetically, I would do something like this:
bool ascending(std::string lhs, std::string rhs) { return lhs < rhs; }
std::sort(my_vector.begin(), my_vector.end(), ascending);
The thing is that this type of sort function is case-sensitive, so would place a string beginning with lowercase 'a' after strings beginning with uppercase 'Z'. The only visible solution I see to this is creating an additional function along the lines of bool ascending_case_insensitive(). However, it would be nice if I could have a function bool ascending() with an additional bool is_case_sensitive parameter to use in sort. Is this possible?
Where you now have
bool ascending(std::string lhs, std::string rhs);
std::sort(my_vector.begin(), my_vector.end(), ascending);
you can have
bool ascending(std::string lhs, std::string rhs, bool case_sensitive);
using namespace std::placeholders;
std::sort(my_vector.begin(), my_vector.end(), std::bind(ascending, _1, _2, false));
The point of std::bind is to return an object that when invoked, calls the bound function, optionally with altered arguments. You can use it to change argument order, add optional parameters, or set parameters to specific fixed values.
Since std::sort takes an instance of the comparison functor, you can use arguments to your functor's constructor determine its behaviour. For example,
class StringCompare
{
public:
StringCompare(bool is_case_sensitive=true) : is_case_sensitive(is_case_sensitive){}
bool operator()(const string&, const string&);///This would handle the comparison using the is_case_sensitive flag
private:
bool is_case_sensitive;
};
std::sort(my_vector.begin(), my_vector.end(), StringCompare(true));//case-sensitive comparison
std::sort(my_vector.begin(), my_vector.end(), StringCompare(false));//case-insensitive comparison
There follows an example that includes a function call with a bound extra parameter and a lambda expression that captures the extra parameter by value:
#include <iostream>// for std::cout
#include <vector>// for std::vector
#include <functional> // for std::bind
#include <algorithm> // for std::sort
bool ltMod(int i, int j, int iMod) {
return (i % iMod) < (j % iMod);
}
int main() {
std::vector<int> v = {3,2,5,1,4};
int iMod = 4;
std::cout << "\nExample for the usage of std::bind: ";
// _1 and _2 stand for the two arguments of the relation iMod is the bound parameter
std::sort(v.begin(),v.end(),std::bind(ltMod,std::placeholders::_1,std::placeholders::_2,iMod));
for( auto i : v ) std::cout << i << ',';
iMod = 3;
std::cout << "\nExample for lambda: ";
// lambdas are unnamed inplace functions
// iMod is captured by value. You can use the value within the function.
std::sort(v.begin(),v.end(),[iMod](int i, int j){ return ltMod(i,j,iMod); });
for( auto i : v ) std::cout << i << ',';
return 0;
}
/**
Local Variables:
compile-command: "g++ -std=c++11 test.cc -o a.exe"
End:
*/
Thought that I would answer my own question in order to summarize the responses I've gotten. So from what I gather, I basically have two options.
The first would be to write a lambda function to handle my one-time case.
// Lambda solution.
std::sort(my_vector.begin(), my_vector.end(),
[](std::string const &lhs, std::string const &rhs) // Thanks for optimizing my example code guys. No, seriously. ;)
{
return boost::toupper(lhs) < boost::toupper(rhs);
});
The second, more reusable option would be to create a functor to handle sort situations like these.
// Functor solution.
class SortAscending
{
private:
bool _is_case_sensitive;
public:
SortAscending(bool is_case_sensitive) :
_is_case_sensitive(is_case_sensitive);
bool operator()(std::string const &lhs, std::string const &rhs)
{
if (_is_case_sensitive)
return boost::toupper(lhs) < boost::toupper(rhs);
else
return lhs < rhs;
}
};
std::sort(my_vector.begin(), my_vector.end(), SortAscending(false));
So think that pretty much sums up my options?

Is this a bug in STL? Why do we need operator overloading in this Structure?

I came across this code for Equal_range, and being very new to C++, it is not clear to me why we need to overload the operator even though we have created a new compare function.
Moreover, could we have used:
bool compare( const S& s, const S& s2 )
{
return s.number < s2.number;
}
instead.
#include <algorithm>
#include <vector>
#include <iostream>
struct S
{
int number;
char name;
S ( int number, char name )
: number ( number ), name ( name )
{}
// only the number is relevant with this comparison
bool operator< ( const S& s ) const
{
return number < s.number;
}
};
struct Comp
{
bool operator() ( const S& s, int i )
{
return s.number < i;
}
bool operator() ( int i, const S& s )
{
return i < s.number;
}
};
int main()
{
// note: not ordered, only partitioned w.r.t. S defined below
std::vector<S> vec = { {1,'A'}, {2,'B'}, {2,'C'}, {2,'D'}, {4,'G'}, {3,'F'} };
auto p = std::equal_range(vec.begin(),vec.end(),2,Comp());
for ( auto i = p.first; i != p.second; ++i )
std::cout << i->name << ' ';
}
EDIT: The link to the code is http://en.cppreference.com/w/cpp/algorithm/equal_range
I think this is a bug in your STL implementation. The C++ standard explicitly states it will not call operator< in this case:
The elements are compared using operator< for the first version, and
comp for the second. Two elements, a and b are considered equivalent
if (!(a<b) && !(b<a)) or if (!comp(a,b) && !comp(b,a)).
"First version" refers to the version where no comparison class is provided. "second" refers to the version you used.
I tried this out on VS2013 but it doesn't even compile. The error is:
error C2664: 'bool Comp::operator ()(const S &,int)' : cannot convert
argument 1 from 'S' to 'int'
Unfortunately, the error is deep down inside Microsoft's obfuscated STL implementation. Why would it call operator()(const S&, int) instead of operator()(int, const S&) ?
So I think Visual Studio's STL implementation is wrong. And if your compiler requires it, I think its STL is also wrong. I wonder if they just never tested this where the third argument is not the same type as what is returned by the iterator.
I would love to hear other people's thoughts on this.
In your example code, your various methods of comparing all do different things; the operator< overload allows you to compare an S with another S, where as the Comp struct allows you to compare an S with an int and vise-versa.
If you read the documentation for equal_range
The elements are compared using operator< for the first version, and
comp for the second
You see that you need to be able to do both a[n] < a[i] and comp(a[n], val), the first to compare items in the list to other items in the list, and the second to be able to compare items in the list to the val parameter of the equal_range call. Hence why you need both an operator overload and a comparison method.

How to create a set with my customized comparison in c++

Could someone explain me what is going on in this example here?
They declare the following:
bool fncomp (int lhs, int rhs) {return lhs<rhs;}
And then use as:
bool(*fn_pt)(int,int) = fncomp;
std::set<int,bool(*)(int,int)> sixth (fn_pt)
While the example for the sort method in algorithm library here
can do like this:
bool myfunction (int i,int j) { return (i<j); }
std::sort (myvector.begin()+4, myvector.end(), myfunction);
I also didn't understand the following:
struct classcomp {
bool operator() (const int& lhs, const int& rhs) const
{return lhs<rhs;}
};
this keyword operator (not being followed by an operator as in a op. overload)... what is the meaning of it? Any operator applied there will have that behavior? And this const modifier... what is the effect caused by it?
I was trying to make a set of C-style string as follows:
typedef struct
{
char grid[7];
} wrap;
bool compare(wrap w1, wrap w2)
{
return strcmp(w1.grid, w2.grid) == -1;
}
set <wrap, compare> myset;
I thought I could create a set defining my sorting function in a similar as when I call sort from algorithm library... once it didn't compile I went to the documentation and saw this syntax that got me confused... Do I need to declare a pointer to a function as in the first example i pasted here?
struct classcomp {
bool operator() (const int& lhs, const int& rhs) const
{return lhs<rhs;}
};
Defines a functor by overloading the function call operator. To use a function you can do:
int main() {
std::set <wrap, bool (*)(wrap,wrap)> myset(compare);
return 0;
}
Another alternative is to define the operator as a part of the wrap class:
struct wrap {
char grid[7];
bool operator<(const wrap& rhs) const {
return strcmp(this->grid, rhs.grid) == -1;
}
};
int main() {
wrap a;
std::set <wrap> myset;
myset.insert(a);
return 0;
}
You're almost there... here's a "fixed" version of your code (see it run here at ideone.com):
#include <iostream>
#include <set>
#include <cstring>
using namespace std;
typedef struct
{
char grid[7];
} wrap;
bool compare(wrap w1, wrap w2) // more efficient: ...(const wrap& e1, const wrap# w2)
{
return strcmp(w1.grid, w2.grid) < 0;
}
set <wrap, bool(*)(wrap, wrap)> myset(compare);
int main() {
wrap w1 { "abcdef" };
wrap w2 { "ABCDEF" };
myset.insert(w1);
myset.insert(w2);
std::cout << myset.begin()->grid[0] << '\n';
}
"explain [to] me what is going on in this example"
Well, the crucial line is...
std::set<wrap, bool(*)(wrap, wrap)> myset(compare);
...which uses the second template parameter to specify the type of function that will perform comparisons, then uses the constructor argument to specify the function. The set object will store a pointer to the function, and invoke it when it needs to compare elements.
"the example for the sort method in algorithm library..."
std::sort in algorithm is great for e.g. vectors, which aren't automatically sorted as elements are inserted but can be sorted at any time. std::set though needs to maintain sorted order constantly, as the logic for inserting new elements, finding and erasing existing ones etc. all assumes the existing elements are always sorted. Consequently, you can't apply std::sort() to an existing std::set.
"this keyword operator (not being followed by an operator as in a op. overload)... what is the meaning of it? Any operator applied there will have that behavior? And this const modifier... what is the effect caused by it?
operator()(...) can be invoked on the object using the same notation used to call a function, e.g.:
classcomp my_classcomp;
if (my_classcomp(my_int1, my_int_2))
std::cout << "<\n";
As you can see, my_classcomp is "called" as if it were a function. The const modifier means that the code above works even if my_classcomp is defined as a const classcomp, because the comparison function does not need to modify any member variables of the classcomp object (if there were any data members).
You almost answered your question:
bool compare(wrap w1, wrap w2)
{
return strcmp(w1.grid, w2.grid) == -1;
}
struct wrap_comparer
{
bool operator()(const wrap& _Left, const wrap& _Right) const
{
return strcmp(_Left.grid, _Right.grid) == -1;
}
};
// declares pointer to function
bool(*fn_pt)(wrap,wrap) = compare;
// uses constructor with function pointer argument
std::set<wrap,bool(*)(wrap,wrap)> new_set(fn_pt);
// uses the function directly
std::set<wrap,bool(*)(wrap,wrap)> new_set2(compare);
// uses comparer
std::set<wrap, wrap_comparer> new_set3;
std::sort can use either a function pointer or a function object (http://www.cplusplus.com/reference/algorithm/sort/), as well as std::set constructor.
const modifier after function signature means that function can't modify object state and so can be called on a const object.