Reputation: 3428
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
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