Hani Gotc
Hani Gotc

Reputation: 900

Design issue with template map with takes different structures

Problem Description and Question

I have a template class Class1. It contains in map in which I want to insert structures A or B.

The problem is that the structures A and B have different types of member variables. Structure A has an std::string member variable whereas structure B has an int member variable.

The comparator is based on structure A. So obviously when I want to insert a structure B it will not compile.

Class1<B,B> c2;
c2.AddElement({1},{1});

How can I fix that design Issue? For instance is it possible to keep Class1 as template class and do something to TestCompare?

I also have a constraint. I cannot modify the structures A and B. they are written in C code. I have no right to change them because they are external codes used by other users. I just simplified the code as much as possible.


Source Code

The code was compiled on cpp.sh

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

typedef struct {
    std::string a;
} A;

typedef struct {
    int b;
} B;


template<typename T1, typename T2> class Class1 {
    public :
      struct TestCompare {
         bool operator()(const T1 & lhs, const T1 & rhs) const {
        return lhs.a < rhs.a;
         }
       };
       Class1() {}
       ~Class1() {}
       void AddElement(const T1 & key, const T2 & value) {
          m.emplace(key, value);
        }
    private :
     std::map<T1,T2,TestCompare> m;
};

int main()
{
  Class1<A,A> c1;
  c1.AddElement({"1"},{"1"});
  
  // Problem here. Obviously it will not compile because the Operator is using 
  // the member variable of struct A.
  //Class1<B,B> c2;
  //c2.AddElement({1},{1});
  //return 0;
}

New Source code

 // Example program
#include <iostream>
#include <string>
#include <map>

typedef struct {
    std::string a;
} A;

typedef struct {
    int b;
} B;


bool operator<(const A & lhs, const A & rhs) {
    return lhs.a < rhs.a;
}

bool operator<(const B & lhs, const B & rhs) {
    return lhs.b < rhs.b;
}

template<typename T1, typename T2> class Class1 {
    public :
    Class1() {}
    ~Class1() {}
    void AddElement(const T1 & key, const T2 value) {
        m.emplace(key, value);
    }
    
     std::map<T1,T2> getMap() {
        return m;
    }
    
    private :
     std::map<T1,T2> m;
};

int main()
{
  Class1<A,A> c1;
  c1.AddElement({"1"},{"1"});
  
  // Problem here. Obviously it will not compile because the Operator is using 
  // the member variable of struct A.
  Class1<B,B> c2;
  c2.AddElement({1},{1});
  c2.AddElement({2},{2});
  
  for(const auto &e: c2.getMap()) {
      std::cout << e.first.b << " " << e.first.b << std::endl;
  }
  return 0;
}

Upvotes: 1

Views: 83

Answers (2)

An0num0us
An0num0us

Reputation: 961

TestCompare requires that every type you use must have a member a that can be compared using <. That's a lot of requirements, which implies a terrible design. Add a 3rd template parameter that will be used to pass a function or a functor that compares the objects

struct CompareA {
    bool operator()(A const & lhs, A const & rhs) const {
        return lhs.a < rhs.a;
    }
};

struct CompareB {
   bool operator()(B const& lhs, B const& rhs) const {
       /*...*/
   }
};

template<typename KeyT, typename ValueT, typename Compare> class Dict {
    public :
       Class1() {}
       ~Class1() {}
       void AddElement(KeyT const & key, ValueT const & value) {
          m.emplace(key, value);
       }
    private :
       std::map<KeyT, ValueT, Compare> m;
};

Dict<A, B, CompareA> dictA;
Dict<B, B CompareB> dictB;

You could specialize the struct TestCompare, like john has suggested in his answer, and provide it as the default template argument

template<typename KeyT, typename ValueT, typename Compare = TestCompare<KeyT>> class Dict { /*...*/ };

Such solution will allow you to provide only 2 arguments, like so

Dict<B, B> dict;

while still maintaining the ability to provide another comparer if necessary.

Upvotes: 1

john
john

Reputation: 88027

I guess you could remove TestCompare from Class1 and template that.

template<typename T> struct TestCompare {
    bool operator()(const T & lhs, const T & rhs) const {
        // default implementation
        return lhs < rhs;
    }
};

template<typename T1, typename T2> class Class1 {
     ...
    private :
     std::map<T1,T2,TestCompare<T1>> m;
}

You could then specialise TestCompare for A and B

template<> struct TestCompare<A> {
    bool operator()(const A & lhs, const A & rhs) const {
        return lhs.a < rhs.a;
    }
};

template<> struct TestCompare<B> {
    bool operator()(const B & lhs, const B & rhs) const {
        return lhs.b < rhs.b;
    }
};

EDIT: Actually you could just use std::less instead of TestCompare. It amounts to pretty much the same thing, and std::map uses std::less by default.

Upvotes: 1

Related Questions