tags:

views:

225

answers:

4

I just wrote some code to test the behavior of std::equal, and came away surprised:

int main()
{
  try
  {
    std::list<int> lst1;
    std::list<int> lst2;

    if(!std::equal(lst1.begin(), lst1.end(), lst2.begin()))
      throw std::logic_error("Error: 2 empty lists should always be equal");

    lst2.push_back(5);

    if(std::equal(lst1.begin(), lst1.end(), lst2.begin()))
      throw std::logic_error("Error: comparing 2 lists where one is not empty should not be equal");
  }
  catch(std::exception& e)
  {
    std::cerr << e.what();
  }  
}

The output (a surprise to me):

Error: comparing 2 lists where one is not empty should not be equal

Observation: why is it the std::equal does not first check if the 2 containers have the same size() ? Was there a legitimate reason?

+9  A: 

Observation: why is it the std::equal does not first check if the 2 containers have the same size() ? Was there a legitimate reason?

How? You do do not pass containers to the function, you pass in iterators. The function has no way of knowing the size of the second container. All it can do is assume bona fide that the user passed in two valid container ranges (i.e. that the second range is correctly specified as the half-open interval [lst2.begin(), lst2.begin() - lst1.begin() + lst1.end()[) and act accordingly.

Konrad Rudolph
You are right, Konrad. I forgot that I was passing the iterator to the std::equal. Having said that, is there something that does something like: equal(lst1, lst2) provided by STL?
ShaChris23
@ShaChris32: yes, you can compare two lists (or in general two standard containers of the same type) with `==`.
Mike Seymour
@Mike: for real? D'oh!
Konrad Rudolph
This is what I hate most about STL. Why does it need to work on iterators all the time. It it was designed to work with containers there would not have been these subtle issues to worry about.
James Dean
That it works with iterators and not containers is in my opinion one of the STL's greatest strengths.
John Dibling
@James: The idea is to split the algorithm from the container, and iterators do that well. That said, Alexandrescu had a talk fairly recently called "Iterators Must Go" that recommends ranges instead of iterators. I agree with him.
GMan
@James: An iterator range is more flexible than a container. It can refer to just part of a container or (with a bit of work) can span more than one container. It doesn't need a container at all (for example, iterators from I/O streams). You can also treat raw pointers as iterators, but you can't treat a raw array as a container.
Mike Seymour
@Konrad: yes, that's one of the Container requirements.
Mike Seymour
@Mike: If I'm not mistaken, both a container **and** an iterator range would qualify as a Range (you can make an iterator_range object that stores the two iterators and makes them accessible through the same `begin()` and `end()` method). It is used in boost, and it is very convenient.
UncleBens
@Mike iterators may be more 'flexible' but it leaves you with warts like std::remove that do not resize the container and leave garbage at the end. The fact that it is based on iterators may be nice to algorithm implementers that need only one implementation for use with many containers. But for application developers you really do not change container types very often and as long as the containers have similar names for adding inserting, removing, searching, comparing even that would not matter.
James Dean
+2  A: 

It's giving you the right answer - you told it to check if the two containers were equal in the range lst1.begin() to lst1.end(). You're still comparing two empty lists as far as equal() is concerned. If you change the code to compare from lst2.begin() to lst2.end(), you'll get what you expected.

Carl Norum
A: 

Because checking the size may be an O(n) operation.

eduffy
No. There is no portable way of knowing the size of the second container. This information is simply not available to `equals`, neither directly nor indirectly.
Konrad Rudolph
John Dibling
@John: How did you get the end iterator for list2?
Bill
I didn't of course. Don't know what I was thinking of there.
John Dibling
The second range doesn't even need to have a size. E.g you have an input iterator that produces random numbers, and you want to compare a bunch of values in say a list against a sequence of those (don't ask me why).
UncleBens
+2  A: 

You can always write your own version of equal that does effectively what you want:

template <class InputIterator1, class InputIterator2>
bool equalx(InputIterator1 first1, InputIterator1 last1,
            InputIterator2 first2, InputIterator2 last2)
{
  while ((first1 != last1) && (first2 != last2))
  {
    if (*first1 != *first2)   // or: if (!pred(*first1,*first2)), for pred version
      return false;
    ++first1; ++first2;
  }
  return (first1 == last1) && (first2 == last2);
}

In order to make sure both ranges have the same number of elements, the signature must include an end to the second range.

R Samuel Klatchko