tags:

views:

53

answers:

3

I can't seem to figure out the syntax for finding structs in containers.

I have a multiset of Event structs. I'm trying to find one of these structs by searching on its key. I get the compiler error commented below.

struct Event {
 public:
  bool operator < ( const Event & rhs ) const {
    return ( time < rhs.time );
  }
  bool operator > ( const Event & rhs ) const {
    return ( time > rhs.time );
  }
  bool operator == ( const Event & rhs ) const {
    return ( time == rhs.time );
  }

  double time;
  int eventID;
  int hostID;
  int s; 
};

typedef std::multiset< Event, std::less< Event > > EventPQ;

EventPQ currentEvents;
double oldRecTime = 20.0;
EventPQ::iterator ceItr = currentEvents.find( EventPQ::key_type( oldRecTime ) ); // no matching function call

I've tried a few permutations to no avail. I thought defining the conditional equality operator was going to be enough.


Solution

After correcting my typo (sorry), I now have a solution closest to AraK's, augmented by Soapbox's suggested use of explicit:

struct Event { 
   explicit Event(double t) : time(t), eventID(), hostID(), s() {}
   Event(double t, int eid, int hid, int stype) : time(t), eventID( eid ), hostID( hid ), s(stype) {}
   ... 
};

EventPQ::iterator ceItr = currentEvents.find( EventPQ::key_type( Event(oldRecTime) ) ); 

I recently discovered that another option would have been to use find_if, discussed here.

Thanks for the help.

+2  A: 

You don't have a suitable constructor that accepts double. Just add the following constructor:

Event(double t) : time(t), eventID(/**/), hostIDeventID(/**/), s(/**/)
{ }

Here is how Event would look like:

struct Event {
 public:
 // Initialize other variables as needed
 Event(double t) : time(t), eventID(/**/), hostIDeventID(/**/), s(/**/)
 { }

  bool operator < ( const Event & rhs ) const {
    return ( time < rhs.time );
  }
  bool operator > ( const Event & rhs ) const {
    return ( time > rhs.time );
  }
  bool operator == ( const Event & rhs ) const {
    return ( time == rhs.time );
  }

  double time;
  int eventID;
  int hostID;
  int s; 
};

// No need for std::less because it is used by default,
// when you define 'operator <' in your class
typedef std::multiset< Event > EventPQ;

EventPQ currentEvents;
double oldRecTime = 20.0;
// You can just pass the double, a temporary object will be created
// for you.
EventPQ::iterator ceItr = currentEvents.find( oldRecTime );
AraK
Could you explain what you mean by this? Let's assume that there is an Event object in the multiset that has its time member equal to oldRecTime. Why would I need a constructor to use find()? (Newbie programmer here.)
Sarah
When you pass `oldRecTime` which is of type `double` to the function, there is no way you can construct an object of type Event out of that unless you have a constructor.
AraK
@Sarah, You need the constructor to create the key you pass to `find`.
Mark Ransom
@AraK. Took me a while to overload the constructor to harmonize this with the rest of my code. Thanks very much, also for pointing out that std::less will be used by default if the < operator is defined.
Sarah
-1: conversion operators should be avoided, especially for a beginner. Also you failed to correct the rather obvious error on the last line.
SoapBox
@SoapBox Would you define `explicit` constructor in this case? Why?! I didn't try to compile the code, but could you clarify what error do you mean?
AraK
@AraK: The error on the last line has nothing to do with the constructor, it's an error in the original code calling .find on the iterator instead of the container. As for the constructor, it should be declared explicit. Not declaring it explicit means the compile is free to convert doubles to "Events" whenever it wants. This can lead to some unexpected behavior and should be avoided unless the conversion between "double" and "Event" is obvious because of how they are related (such as conversion between std::string and const char*)
SoapBox
@SoapBox I wouldn't argue about the usefulness of implicit conversions, but Thanks for telling me about the error :)
AraK
No one is saying they aren't useful, but like overloaded operators they have their place and are often misused. This is an example of misuse since it seems pretty clear the original question has no idea what is actually going on with your solution, just that it works.
SoapBox
@SoapBox - Not very charitable, as I don't accept solutions until I understand them fairly well. (I also appreciate knowing best practices, even if I can't immediately implement them.) Thanks for suggesting 'explicit.' I've written it up into my solution, now an addendum to the original question.
Sarah
+1  A: 

Besides the missing constructor, you don't want to call find() on the iterator ceItr but on currentEvents:

EventPQ::iterator ceItr = currentEvents.find(EventPQ::key_type(oldRecTime));

Note that find() only gives you an iterator to the first match, use equal_range() to get a range of all matches:

std::pair<EventPQ::iterator, EventPQ::iterator> result;
result = currentEvents.find(EventPQ::key_type(oldRecTime));

for(EventPQ::iterator it = result.first; it != result.second; ++it) {
    // do stuff
}
Georg Fritzsche
Sorry, that was a typo! Thanks for the equal_range() reminder--this will eventually be a set with unique keys.
Sarah
A: 

You seem to have multiset and multimap confused. Multiset is for when the key and the value are one-and-the-same. Multimap is for when a key and a value are associated, but not the same object.

In this case, Event isn't actually the key. The "time" double appears to be the key. Since the key and the value are not exactly the same, you should use a multimap. Using event as both the key and value doesn't make sense here.

You don't want to construct an event with extra fields you don't need just to search for a given value. Instead, multimap lets you search using the double, which is what you really want. This also eliminates the need for less than operators in the Event class.

Your code would then look something like this:

struct Event {
  double time;
  int eventID;
  int hostID;
  int s; 
};

typedef std::multimap<double, Event> EventPQ;

EventPQ currentEvents;
double oldRecTime = 20.0;
std::pair<EventPQ::iterator, EventPQ::iterator> results = currentEvents.equal_range(oldRecTime);

for(EventPQ::iterator cur = results.first; cur != results.second; ++cur) {
    // do something to *cur
} 
SoapBox
I really am trying to use a multiset, sorting on the time component of the Event structs (the 'key' I defined in the operators).
Sarah
You can do that with a multimap. You use the time as a key, and the rest of the event as a value. So to insert "Event e" into the multimap you would do: currentEvents.insert(std::make_pair(e.time,e));
SoapBox
Right, but I need the time attribute to stay in the Event object because of other stuff happening in the program, and copying it into a multimap would create redundancy.
Sarah