1

As far as I know we should never compare two const character strings using relational operators <>... because the fact that it compares the addresses rather than the values:

const char* sz1 = "Hello";
const char* sz2 = "hello";
if(sz1 < sz2);// not valid. So use strcmp instead.
  • What I've noticed that Ordered Associative Containers like map, multimap, set, multiset impose a restriction on their key so that the key should some how be compared to order the elements in the container. The default operator for the key is < operator.

Everything is clear until I've created a map, set of const char* then I get the results incorrect:

std::set<const char*> scp{ "Hello", "World", "C++", "Programming" };    
std::set<std::string> sstr{ "Hello", "World", "C++", "Programming" };

// error
std::copy(scp.cbegin(), scp.cend(), std::ostream_iterator<const char*>(std::cout, " "));
std::cout << std::endl;

// Ok 
std::copy(sstr.cbegin(), sstr.cend(), std::ostream_iterator<std::string>(std::cout, " "));
std::cout << std::endl;
  • It is apparent that scp compares pointers to character strings while sstr is Ok as long as class string has defined < to work properly.

  • Why STL allows this? (creating associative containers whose key element type is a char*) and why here's even no warning?

Quentin
  • 62,093
  • 7
  • 131
  • 191
Itachi Uchiwa
  • 3,044
  • 12
  • 26
  • 2
    Counter-argument: why bog down standard containers with provision against what *might* be a pointer to a C-style string, since you're not supposed to use C-style strings in C++ anyway? – Quentin Oct 01 '19 at 12:15
  • 2
    Just because it won't work for this simple case doesn't mean someone else won't have a use-case where pointers as keys might not be useful. Besides, your case can be solved by providing your own comparison function that uses `strcmp`. – Some programmer dude Oct 01 '19 at 12:17
  • 2
    I even think that `sz1 < sz2` is UB (whereas `std::less<>{}(sz1, sz2)` is not). – Jarod42 Oct 01 '19 at 12:18
  • @Jarod42 unless they point into the same array, yes. – Quentin Oct 01 '19 at 12:19
  • 1
    You might want to compare only pointers. Why forbid that (even if unusual)? And for warning, what would be the correct way to express the intent to compare pointers? – Jarod42 Oct 01 '19 at 12:24

2 Answers2

8

The default operator for the key is < operator.

This is not true. The default comparison operator for the non-hashed associative containers is std::less. std::less uses operator < for comparisons, but with one key difference. Unlike the pointers built in operator < where

neither pointer is required to compare greater than the other.

source

std::less has that the

specializations for any pointer type yield a strict total order that is consistent among those specializations and is also consistent with the partial order imposed by the built-in operators <, >, <=, >=.

source

So this is a safe operation and we can reliably store pointers in the map.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • But in C++ primer 5 ed by Lipman: "The associative containers place constraints on the type that is used as a key. We’ll cover the requirements for keys in the unordered containers in § 11.4 (p. 445). For the ordered containers—map, multimap, set, and multiset—the key type must define a way to compare the elements. By default, the library uses the < operator for the key type to compare the keys." "Requirements on Key Type". – Itachi Uchiwa Oct 01 '19 at 20:41
  • 1
    @ItachiUchiwa He over simplified/was mistaken. By default it uses `std::less` which uses `operator <` but with some more guarantees when it comes to pointers. You can see [in the C++ reference](https://en.cppreference.com/w/cpp/container/set) that `std::set` uses `std::less` for `Compare` – NathanOliver Oct 01 '19 at 20:43
  • Ok. Thanks so much! – Itachi Uchiwa Oct 01 '19 at 20:45
0

As others have pointed out, maybe sometimes you want pointer comparisons, and if you don't then the container allows you to supply your own custom comparison operator like this:

#include <cstring>
#include <iostream>
#include <iterator>
#include <string>
#include <set>

struct CStrCmp {
    bool operator() (const char* lhs, const char* rhs) const {
        return strcmp(lhs, rhs) < 0;
    }
};
int main()
{
    std::set<const char*, CStrCmp> scp{ "Hello", "World", "C++", "Programming" };
    std::set<std::string> sstr{ "Hello", "World", "C++", "Programming" };

    // This works too now
    std::copy(scp.cbegin(), scp.cend(), std::ostream_iterator<const char*>(std::cout, " "));
    std::cout << std::endl;

    // Ok 
    std::copy(sstr.cbegin(), sstr.cend(), std::ostream_iterator<std::string>(std::cout, " "));
    std::cout << std::endl;
}
Frodyne
  • 3,547
  • 6
  • 16