2

So I have two classes containing std::map members with effectively identical functionality except that the ordering of one map is std::less and the other std::greater.

If I create an abstract parent class and declare a single map member, is there any way to dynamically assign the comparator for this member in the derived class constructors? That way the functionality can obviously all reside in the parent class.

aerospatiale
  • 265
  • 2
  • 12

3 Answers3

4

You can't change the comparator after the fact. But you can use the same comparator class and get either "greater" or "less" at the time of construction. You just need a stateful comparator:

struct my_compare {
    enum compare_type { less, greater };
    explicit my_compare(compare_type t) : m_type(t) {}
    template<class T, class U>
    bool operator()(const T& t, const U& u) const {
        if(m_type == less) { return t < u; }
        else { return t > u; }
    }
    compare_type m_type;
};

Then you can do

std::map<int, int, my_compare> less_map((my_compare(my_compare::less)));
std::map<int, int, my_compare> greater_map((my_compare(my_compare::greater)));

The extra pair of parentheses is because it would otherwise be the most vexing parse , even though a function parameter declaration cannot have a qualified name. In C++11, list-initialization (my_compare{mycompare::less}) can be used instead.


For your specific design, an implementation might look like

class A {
protected:
    explicit A(my_compare::compare_type ct) : my_map(my_compare(ct)) {}
    std::map<int, int, my_compare> my_map;
};

class B_less : public A{
public:
    B_less() : A(my_compare::less) {}
};
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I tried the following but get a semantic error "no matching constructor for initialization key_compare" `class A { public: A() {} void DoSomethingWithMyMap() {}; protected: std::map my_map; }; class B_less: public A { public: B_less () { my_map = std::map (my_compare(my_compare::less)); } }; class B_greater : public A { public: B_greater () { my_map = std::map (my_compare(my_compare::greater)); } }; int main(int argc, const char * argv[]) { return EXIT_SUCCESS; } ` – aerospatiale Mar 10 '15 at 07:53
  • The initialization will need to be done in `A`'s constructor's member initialization list (so you need to give `A` a constructor that takes a `my_compare::compare_type` and have `B`'s constructor call that constructor); alternatively, you can give `my_compare`'s constructor a default argument, but that's less efficient. – T.C. Mar 10 '15 at 08:20
1

No. The comparison function is used to generate the actual data structure -- changing the comparison function would require rebuilding the structure from scratch.

That said, if all you want to do is iterate the structure in reverse order, map is a reversible container, so you can just loop over the structure normally using the reverse iterators.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Thanks for the swift response Billy. I don't need to iterate the structure in this case, aside from direct key access and insertion I just want to be able to access the top element of the map. I like the idea though. How would this be implemented dynamically so that the functions themselves can all be defined in the base class (just redefining the constructor)? Wouldn't I also have to define the type of std::iterator when I declare it in the base class? If not and I redefined it as a std::reverse_iterator> in one derived class, what do I refine it as in the other derived class? – aerospatiale Mar 10 '15 at 03:11
  • Also, regarding your comment on changing the comparison function requiring rebuilding the structure from scratch, is that possible then? Because my parent class is abstract and whenever one of the derived classes is constructed, their map structure will be strictly empty anyway. – aerospatiale Mar 10 '15 at 03:17
1

You can do what you want to do by creating a custom functor class that uses less or greater depending on some state. Here's an example:

#include <iostream>
#include <string>
#include <map>

struct MyCompare
{
   MyCompare(bool useLess) : useLess_(useLess) {}

   bool operator()(int lhs, int rhs)
   {
      if ( useLess_ )
      {
         return (lhs < rhs);
      }
      else
      {
         return (lhs > rhs);
      }
   }
   bool useLess_;
};

int main(int argc, char** argv)
{
   std::map<int, std::string, MyCompare> myMap1(MyCompare(true));
   std::map<int, std::string, MyCompare> myMap2(MyCompare(false));

   myMap1[1] = "abcd";
   myMap1[2] = "lmnop";
   myMap1[3] = "xyz";

   myMap2[1] = "abcd";
   myMap2[2] = "lmnop";
   myMap2[3] = "xyz";

   std::cout << "Map 1: " << std::endl; for ( auto const& v : myMap1 )
   {
      std::cout << "Key: " << v.first << ", Value: " << v.second << std::endl;
   }

   std::cout << "Map 2: " << std::endl;
   for ( auto const& v : myMap2 )
   {
      std::cout << "Key: " << v.first << ", Value: " << v.second << std::endl;
   }

   return 0;
}

Output:

Map 1: 
Key: 1, Value: abcd
Key: 2, Value: lmnop
Key: 3, Value: xyz
Map 2: 
Key: 3, Value: xyz
Key: 2, Value: lmnop
Key: 1, Value: abcd

In your case, you can pass a flag from the child class to the parent class indicating what value to use to create the compare functor.

R Sahu
  • 204,454
  • 14
  • 159
  • 270