Search code examples
c++templatesgeneric-programming

C++ how to declare an self defined array in generic programming


I have a class template

template <class T> class Collection
{
private:
    int size;
    int type;
    T* Arr;
    int Case;

public:

void ArrayGenerating() {
    switch(type) {
    case 1: 
        Arr = new T[size];

        for (int i = 0; i < size; i++) {
            srand((unsigned)time(NULL)); 
            Arr[i] = static_cast <T> (rand()) % size;
        }

    case 2:
        Arr = new T[size];

        for (int i = 0; i < size; i++) {
            srand((unsigned)time(NULL)); 
            Arr[i] = static_cast <T> (rand()) / (static_cast <T> (RAND_MAX/size));
        }

    case 3:
        Arr = new T[size];

        for (int i = 0; i < size; i++) {
            srand((unsigned)time(NULL)); 
            Arr[i].setNumerator(static_cast <int> (rand()) % size);

            srand((unsigned)time(NULL));
            Arr[i].setDenominator(static_cast <int> (rand()) % size);
        }

    }
  }
};

I want to creat an random array of generic data type

with type 1, that is an interger array. type 2, an float array. type 3, I have an self-defined data type "fraction". But when I compile the programm, there are errors:

Error   1   error C2228: left of '.setNumerator' must have class/struct/union

Error   2   error C2228: left of '.setDenominator' must have class/struct/union

So if there are any solution for this complication?


Solution

  • I guess, type is a constant depending on T. Otherwise it would make no sense to have a T* point to an int, when T is a float. If that is true, it is not necessary at all.

    I think, what you are looking for is template specialization (untested code):

    // this is common to all cases.
    class CollectionBase {
      protected:
        int size;
    };
    
    // the general template is not defined
    // the compiler will complain whenever T is neither int, nor float, nor fraction.
    template<class T> class Collection;
    
    // here come the specializations
    template<> class Collection<int>: private CollectionBase
    {
      private:
        int* Arr;    
      public:
        void ArrayGenerating() {
          Arr = new int[size];
          for (int i = 0; i < size; i++) {
            srand((unsigned)time(NULL)); 
            Arr[i] = static_cast<int>(rand()) % size;
          }
        }
    };
    
    template<> class Collection<float>: private CollectionBase
    {
      private:
        float* Arr;
      public:
        void ArrayGenerating() {
          Arr = new float[size];
    
          for (int i = 0; i < size; i++) {
              srand((unsigned)time(NULL)); 
              Arr[i] = static_cast<float>(rand()) / (static_cast<float>(RAND_MAX/size));
          }
        }
    };
    
    template<> class Collection<fraction>: private CollectionBase
    {
      private:
        fraction* Arr;
      public:
        void ArrayGenerating() {
          Arr = new fraction[size];
    
          for (int i = 0; i < size; i++) {
              srand((unsigned)time(NULL)); 
              Arr[i].setNumerator(static_cast <int> (rand()) % size);
    
              srand((unsigned)time(NULL));
              Arr[i].setDenominator(static_cast <int> (rand()) % size);
          }
        }
    };
    

    Please note, that this kind of code is dangerous. Consider std::vector<> instead of managing dynamically allocated array yourself.

    Also be aware, that as a rule of thumb all methods of your class should be safely callable as soon as the constructor has finished. In your code any function that accesses Arr uses a random pointer to some memory, before ArrayGenerating() has run. Whenever you call ArrayGenerating() twice for some reason, your code will leak memory, because you never bother to delete[] your array before creating a new one.

    The best tool C++ gives you for memory management is constructors and destructors. You are best of, when you encapsulate every resource, that you have to release once in a while, in a handler object. In this case std::vector already does what you need.

    So here is a full (yet untested) most generic solution for you. I'd start with a free function to create random numbers:

    template<typename T> struct dist{
        using uniform = std::uniuniform_int_distribution<T>;
    };
    template<> struct dist<float> {
        using uniform = std::uniuniform_real_distribution<float>;
    };
    
    template<typename T>
    std::vector<T> createRandomNumbers(size_t s) {
        auto e1 = std::default_random_engine{std::random_device{}()};
        auto u = dist<T>::uniform{0, static_cast<T>(s)};
    
        auto r = std::vector<T>(s, 0);
        for( auto& i: r ) i = u(e1);
    
        return r;
    }
    // fraction need a specialization
    template<>
    std::vector<fraction> createRandomNumbers<fraction>(size_t s) {
        auto e1 = std::default_random_engine{std::random_device{}()};
        auto u = dist<int>::uniform{0, static_cast<int>(s)};
    
        auto r = std::vector<fraction>(s, 0);
        for( auto& i: r ) {
              i.setNumerator(u(e1));
              i.setDenominator(u(e1));
        }
    
        return r;
    }
    

    Now we implement a Collection class template like yours, if we really still need it:

    template <typename T> Collection {
        private:
            // this will handle all your memory management needs
            std::vector<T> randoms;
        public:
            Collection(size_t s) :
                randoms{createRandomNumbers<T>(s)}
            {};
    
            createNewRandoms(size_t s) {
                std::swap(randoms, createRandomNumbers<T>(s));
            };
            // whatever else is necessary
    };