2

I have 6 types of datastructure<T>.

All of them contains cover 10-20 common functions (purple) e.g. addFirst().

However, some datastructures has its own 0-5 unique functions (pink).
For example, only MyArray<T> has function addManual(T,int).
In rare cases, some different types of datastructure support a few same special function.

enter image description here
(The real-case table is ~ 10x more complex.)

Here is a short snippet :-

class Vector3D{};
template<class T>class MyArray{
    public: void addFirst(T t){/* some code */}
    /* other add / remove / query function */
    public: void addManual(T t,int internalIndex){/* some code */}
};
template<class T>class MyGrid3D{
    public: void addFirst(T t){/*some code */}
    /* other add / remove / query function */
    public: void addSpecial(T t,Vector3D gridAddress){/* some code*/}
};

It works good so far.
Now, I want to make it usage easier by providing interface Adapter<MyX,T,I>.
The user of my library can identify the internal stored type T and the interface I separately.

Whenever a parameter of function (purple/pink) has type I, I will convert it to T and internally manipulate it as if it has type T.

Here is my prototype :-

template<template<class> class MyX,class T,class I>class Adapter{
    private: T convert(I i){/* some logic */ return T();}
    MyX<T> database;
    public: void addFirst(I i){
        database.addFirst(convert(i));
    }
};    
int main() {
    Adapter<MyArray,int,std::string> adapter;
    adapter.addFirst(std::string("5.0f"));
    std::cout<<"compiled OK";
    return 0;
}

It works good (coliru demo), but it doesn't cover special function (purple) e.g. addManual() or addSpecial() .

Question

What is a design pattern / C++ magic to make Adapter support those functions?

Here are my three poor solutions :-

  1. Create many adapter class for each type of datastructure, e.g. AdapterMyArray<T,I> and AdapterMyGrid3D<T,I>.

  2. Improve the first solution by inheritance e.g. AdapterMyArray<T,I> and AdapterMyGrid3D<T,I> derived from AdapterBase<T,I>.

  3. Check type of MyX and use std::enable_if to enable instantiate of those special functions.

I feel that all my solutions are somehow too manual and it will cause some maintainability issue.

Edit: Yakk's solution is good, but I still doubt if there are even better solution.

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • So you have different classes with different member functions and want to treat them all the same without losing specialness. This cannot work. Either you treat them the same and give up specialness or you treat every class special and lose generality. The best you can do is provide a generic Adapter that gives access to generic functions and a way to do special casing for the special functions when needed. Maybe you can add some generality, like doing nothing is an acceptable implementation for special functions that are not implemented. – nwp May 11 '17 at 12:03
  • You can still add special members directly inside `Adapter`. You will have errors when you want to use special members on "incorrect" `Mx`. Your alternatives should gives better error message though. – Jarod42 May 11 '17 at 12:06
  • For `3.`, instead of checking type, you may check functionality (with custom trait `has_member_addSpecial::value`). – Jarod42 May 11 '17 at 12:08
  • @nwp As you know, it is not acceptable (by me), but "doing nothing" is an interesting idea! Thank. – javaLover May 11 '17 at 12:09
  • @Jarod42 Thank, that is an improvement, more readable in some situation and cute! – javaLover May 11 '17 at 12:09

1 Answers1

1

CRTP.

Have Adapter_addFirst Adapter_addManual etc CRTP helpers. They use CRTP to access database<T> and implement their one (or set of) functions.

Adapter queries database or database<T> via traits class helpers to determine which of the CRTP Adapter_*s it should inherit (publicly) from.

template<class D, class I>
class Adapter_addFirst {
  D* self() { return static_cast<D*>(this); }
  D const* self() const { return static_cast<D*>(this); }
public:
  void addFirst(I i){
    self()->database.addFirst(self()->convert(i));
  }
};
template<std::size_t I>
struct empty_t {};
template<template<class> class MyX,class T,class I>
class Adapter:
  public std::conditional_t<true, Adapter_addFirst<Adapter<MyX, T, I>, I>, empty_t<0>>
{
  friend struct Adapter_addFirst<Adapter<MyX, T, I>, I>;
private:
  T convert(I i){/* some logic */ return T();}
  MyX<T> database;
};

where true is replaced with a test of "should I have addFirst".

Repeat for each of the methods.

Dispatch is static, methods only exist if they should, everything is standard compliant.

You can test for "should I addManual" either via tags on your types, type traits, or even SFINAE test of a call to std::declval<MyX<T>&>().addManual( std::declval<T const&>() ). For the SFINAE case, see can_apply or equivalent.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Why templated `empty_t` ? – Jarod42 May 11 '17 at 21:26
  • @Jarod42 Need to inherit from something for `std::conditional` to work there. I want more than one thing (conditionally), and cannot inherit from the same thing more than once in the same list. So each `conditional` would get a different `empty_t`. – Yakk - Adam Nevraumont May 11 '17 at 21:36
  • So, `empty_t, I>>` (type instead of size_t) would avoid to have the manual matching number/type. Thanks for clarification (which should have been in answer IMO). – Jarod42 May 11 '17 at 21:51