4

I have something like this:

template <typename T, int N> class Data {
    typedef Data<T, N> data;
    public:
        T y;
        data *ptr[N];
};

And what I would like to able to add variables and functions to the class if N == 2, for example:

template <typename T, int N> class Data {
    typedef Data<T, N> data;
    public:
        T y;
        data *ptr[N];

        /* if N == 2, add the following members to the class */
        int x;
        data *left()  { return ptr[0] };
        data *right() { return ptr[1] };
};

I know one can use inheritance or specialization, but there is problems on both of them.

Inheritance doesn't make any sense in this specific case because the variable ptr points to the same class, so if I use:

template <typename T> class Data2 : Data<T, 2> {
    typedef Data2<T> data;
    public:
        int x;
        data *left()  { return ptr[0] }; // Error (ptr has type Data<T,N>)
        data *right() { return ptr[1] }; // Error
};

the variable ptr that Data2 inherits from Data would point to the class Data and not Data2, rendering the base class useless.

The problem with specialization is that I would have to copy the entire base class to the specialization, and for smaller cases this might be okay, but if I have a really complex class or if I want custom variables and functions for several values of N, that would be impractical.


I also know the existence of std::enable_if which I think can solve the problem for functions, but it does not so for variables.

If anyone has a solution for it or a different approach that would bypass this, please let me know.

gmardau
  • 389
  • 2
  • 11

3 Answers3

4

You can specialize your template declarations for particular values of N, and use another level of inheritance indirection for the common code in your base class, that doesn't depend on N:

template <typename T> class DataBase {
    public:
        T y;
};

template <typename T, int N> class Data : public DataBase {
    typedef Data<T, N> data;
    public:
        data *ptr[N];
};

Also, without really knowing what you actually want there, I'd say something like

typedef T DataType;
typedef T* DataTypePtr;

in the base class makes more sense for me.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • That would not work, because if I create an instance of class `Data`, I would have access to the variable `y`, and I only want to have a variable `y` **if** `N = 2` (in the original post it was `int x` and not `T y`) – gmardau Jul 06 '16 at 22:08
  • @Mehlins Please make more clear what's the purpose of `y` and `x` in your class design. You could still resort to the [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) to inject the definition of `x` from your inherited class. – πάντα ῥεῖ Jul 06 '16 at 22:13
  • Ok, I would want my class to have both the variable `x` and the functions `left()` and `right()` if and only if `N=2`. The variables `y` and `ptr` are to be there whatever it is the value of `N`. – gmardau Jul 06 '16 at 22:16
2

Inheritance doesn't make any sense in this specific case

It depends if it's used alone or if it's used with a mix of friend class declaration and CRTP idiom:

template<typename, int>
struct BaseData { };

template<typename T>
struct BaseData<T, 2> {
    int x;
    typename T::data *left() { return static_cast<T*>(this)->ptr[0]; }
    typename T::data *right() { return static_cast<T*>(this)->ptr[1]; }
};

template <typename T, int N>
class Data: public BaseData<Data, N> {
    friend class BaseData<Data, N>;
    using data = Data<T, N>;

public:
   T y;
    data *ptr[N];
};
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Ok, I was fiddling with this for a bit and your solution also doesn't work. I understood what you meant with the CRTP, but first of all, the `typename T` in the `BaseData` declaration is not a typename but a template instead, and the declaration should be `template – gmardau Jul 07 '16 at 01:41
  • @Mehlins Try to show a meaningful example next time. Otherwise, it's impossible to help guessing that *data is something different*. That said, I don't think it's impossible, it would probably require a bit of extra work. – skypjack Jul 07 '16 at 06:27
0

As skypjack mentioned, it is possible with a CRTP. A "working" example coming close to your example would look like this:

template <typename T, int N, typename Derived >
class DataBase {
    public:
        typedef Derived data;
        T y;
        typename DataBase<T,N,Derived>::data *ptr[N];
};


template <typename T, int N>
class Data : public DataBase< T, N, Data<T,N> > {
};


template <typename T>
class Data2 : public DataBase< T, 2, Data2<T> > {
    public:
        int x;
        typename DataBase<T,2,Data2<T> >::data* left()  { return DataBase<T,2,Data2<T> >::ptr[0]; }
        typename DataBase<T,2,Data2<T> >::data* right() { return DataBase<T,2,Data2<T> >::ptr[1]; }
};

int main() {


  Data2<double> myData;

  Data<double,5> myData5;

  myData.y = 3.0;
  myData.x = 5;

  myData.left();
  myData.right();

  myData5.y = 42.0;

}

A CRTP of course implies that you never can directly instantiate the class DataBase directly without having a derived class. If this is not what you need, please give the broader context to understand the situation a bit better.

EDIT: I have changed the code to include a generic derived class with no extra features.

  • Sorry, your example also doesn't work, because I want to be able to instantiate the class `Data` with other values for `N` beyond `2`. In your example I would have to create an infinite number of classes that inherit from `DataBase`, one for every possible value of `N`. – gmardau Jul 07 '16 at 14:45
  • You can add a templated derived class for the unaltered case. – Martin Hierholzer Jul 08 '16 at 08:49