Fanatic23
Fanatic23

Reputation: 3428

How to use a policy to add an additional layer of functionality in the class?

Recently I had to design a class that is built on top of another template class. Kind of:

template<
  typename T1, 
  template <class> class A, 
  ..> 
class B { 
  A<T1> a1; 
  // other members; 
public:
  bool insert(T1 const & value);
  bool update(T1 const & value);
  bool delete(T1 const & value);
  bool contains(T1 const & value); 
};

My original plan was to only provide value semantics and this interface was just fine. A recent requirement came up and I am tasked to provided a keyed lookup in addition to the existing functionality. I am looking for solutions to do this in a clean way.

Just adding in bool find(KeyType const & key, ValueType & value) does not seem to be correct. Whether a keyed lookup should even be there should be dependent on some policy that the user will decide - so the find method itself will exist depending on the policy.

How best to do this?

Upvotes: 1

Views: 129

Answers (1)

JorenHeit
JorenHeit

Reputation: 3997

There are multiple approaches to this, but I think this might work for you. The idea is that you define a base-class, from which your class (B) inherits. The base-class is a class-template with one bool parameter, and depending on the value of this parameter it does or does not define a find-member. The value that is passed as the template argument depends on a type-trait that you define for a couple of possible types of A.

Warning: template metaprogramming below.

Let's start with the type traits. I'll assume that possible types of A (which is a template template parameter) are things like vector, map, etc:

template <template <typename ...> class Container>
struct EnableKeyFind
{
  static int const value = false; // fallback                                                                                                                                           
};

template <>
struct EnableKeyFind<std::map>
{
  static int const value = true;
};

template <>
struct EnableKeyFind<std::unordered_map>
{
  static int const value = true;
};

In words, the above states that there should be a find member defined when A == std::map or A == std::unordered_map. In all other cases, the non-specialized template is instatiated, for which value == false, i.e. no find-member. You can add other specializations to extend this principle.

Next, let's define the base-class:

template <typename Container, bool Enable>
class Base
{
  Container &d_cont; // Base-class should have access to the container                                                                                                                  
public:
  Base(Container &cont): d_cont(cont) {}

  template <typename KeyType, typename ValueType>
  bool find(KeyType const &key, ValueType const &val);

  /* An alternative implementation would be where you use 
     Container::key_type and Container::value_type and get rid of
     the function-template. This would require the Container-class 
     to have STL-like semantics. Pick whichever you prefer. */
};

// Specialization for 'false': no implementation                                                                                                                                        
template <typename Container>
class Base<Container, false>
{
public:
  Base(Container &cont) {}
};

The base-class is specialized for the case that the Enable template non-type parameter is false. In this case, its implementation is almost trivial (with the exception of the constructor that still needs the same signature).

Now let's look at the modified B class (I kept the non-descriptive name to stay close to the question):

template <template <typename ...> class A, typename ... Types>
class B: public Base<A<Types ...>, EnableKeyFind<A>::value>
{
  typedef A<Types ...> AType;
  typedef Base<AType, EnableKeyFind<A>::value> BaseType;

  AType a1;
public:
  B():
    BaseType(a1) // provide the base with access to a1
  {}
};

Because different container-types, like vector and map, demand a different number of template parameters, I generalized the class-template a little by making it a variadic template. It can now store a container of any type. It inherits from Base, and uses the type-trait EnableKeyFind<A> to determine if it should inherit from Base<Container, true> or (the specialization) Base<Container, false>.

A typical use-case:

int main()
{
  B<vector, int> b1;
  b1.find(1, 1); // error: has no member find                                                                                                                                            

  B<map, string, int> b2;
  b2.find(1, 1);    
}

I hope this helps. It was a fun little exercise anyway.

A possible downside to this approach might be that your user should specialize the EnableKeyFind class-template for their specific container if they want find() to be available. I don't know if this is too much to ask from your user, but you can always provide them with a macro if you want to hide the details:

define ENABLE_KEYFIND(Container) \
  template <>\
  struct EnableKeyFind<Container>\
  { static int const value = true; };

Upvotes: 1

Related Questions