0

I would like to use a standard map with the following custom key:

struct ParserKey{
    ParserKey(uint16_t compno,
             uint8_t resno,
             uint64_t precinctIndex) : compno_(compno),
                                       resno_(resno),
                                       precinctIndex_(precinctIndex)
    {
    }
     uint16_t compno_;
     uint8_t resno_;
     uint64_t precinctIndex_;
};

There's no obvious way of ordering the keys, though. Can these keys be ordered, or do I need a different associative collection ?

4
  • 1
    What about std::unordered_map then? Commented May 30, 2022 at 18:26
  • 1
    If the order doesn't matter, then you could use an unordered_map, but then you'd have to provide a way to hash it. If you don't want to do that, and the order doesn't matter, then just sort them based on their 3 values. Commented May 30, 2022 at 18:27
  • 1
    "There's no obvious way of ordering the keys, though" - in principle? There may not be, but what is stopping you from simply ordering by compno_ first, by resno_ second and by precinctIndex_ third? All things considered, you may not even seek ordering at all. In that case, you may find std::unordered_map useful. Commented May 30, 2022 at 18:28
  • 1
    Regarding the term "stl" you should read What's the difference between "STL" and "C++ Standard Library"?. Commented May 30, 2022 at 19:13

3 Answers 3

6

If you don't care about the specific order and just want to satisfy the requirements for sorting, a common and simple pattern is to use std::tie with all of the class members of the compared instances, and compare those results instead.

std::tie creates a std::tuple of references to the members, and std::tuple implements operator< which compares its elements (the members of the object in this case) lexicographically.

In your case, using member operator< :

bool operator<(const ParserKey & other) const
{
    return std::tie(compno_, resno_, precinctIndex_) < 
        std::tie(other.compno_, other.resno_, other.precinctIndex_);
}

Live example https://godbolt.org/z/v433v54jz

Sign up to request clarification or add additional context in comments.

5 Comments

This is a better answer than mine. But why use std::tie instead of std::tuple directly? Grabbing the reference seems unnecessary. Copying the int should be fine.
@bitmask Using std::tie is more generic, it will work as expected with class types. This is what std::tie is meant for, bundling several heterogeneous objects that already exist to allow handling them as a single set. If the cost of dererencing isn't optimized out, it is still probably not significant. Until a profile shows std::tie is a problem for an application, I would consider using std::tuple a premature optimization. Remember that this answer introduces a pattern that can be applied to other use cases, not just an answer specifically for the code shown in the question.
I find it surprising that comparison of std::tuple pairs is defined because hashing a tuple is not ... has comparison of tuples always been defined?
@jwezorek It has been defined since c++11 which introduced std::tuple. It shouldn't be surprising, most container and container-like standard types provide all the comparison operators, but very few come with support for std::hash. This is more of a problem with std::hash than std::tuple specifically.
Yeah, i just feel like since tuples are value types it is natural to want to use them as keys to unordered maps and well as ordered maps
4

You can just impose any arbitrary total order on these integers in the same style as std::lexicographical_compare does.

So, the lazy approach is:

// inside ParserKey ...

std::array<std::uint16_t,3> to_array() const {
  return {compno_, resno_, precinctIndex_};
}

friend bool operator<(ParserKey const& lhs, ParserKey const& rhs) {
  auto const l = lhs.to_array();
  auto const r = rhs.to_array();
  return std::lexicographical_compare(begin(l),end(l), begin(r), end(r));
}

But it carries the overhead of pressing your members into an iterable container. If you don't want that, you might have to reimplement a lexicographical compare yourself.

3 Comments

operator<=> will do that automatically. No need to turn the fields into an iterable container. That requires C++20 though.
@Fureeish Really?! Huh. Well, my brain isn't used to the spaceship, yet.
Really :) auto operator<=>(ParserKey const&) const = default; will generate <, <=, > and >= with respect to the fields in their order of declaration. It's great!
3

In C++20 you can do this: (at class scope)

friend auto operator<=>(const ParserKey &, const ParserKey &) = default;

Don't forget to #include <compare>.

This will give you all 6 relational operators: ==, !=, <, <=, >, >=, performing lexicographical comparison (same as in @FrançoisAndrieux's answer).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.