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?

Upvotes: 1

Views: 272

Answers (3)

cdonat
cdonat

Reputation: 2822

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
};

Upvotes: 2

Christian Hackl
Christian Hackl

Reputation: 27538

Note: I won't comment on your use of C functions or other problems. Others have already told you how to avoid them.


A type member in a class template somehow defeats the purpose of generic programming, doesn't it? You should get rid of your type and replace the switch with template specialisation.

Here is a simple example to get you started:

// Collection for all T except of `fraction`
template <class T> class Collection
{
private:
    int size;
    T* Arr;
    int Case;

public:
    void ArrayGenerating() {
        Arr = new T[size];

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

// Collection for `fraction`
template <> class Collection<fraction>
{
private:
    int size;
    fraction* Arr;
    int Case;

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);
        }
    }
};

This is the simplest kind of template specialisation but may lead to a lot of code duplication. You can get around this problem, too, though. For example, you could extract all common parts into a common private base class, something like this:

namespace detail
{
    template <class T> class CollectionBase
    {
    protected:
        int size;
        T* Arr;
        int Case;
    };
}

template <class T> class Collection : detail::CollectionBase<T>
{
public:
    void ArrayGenerating() {
        Base::Arr = new T[Base::size];

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

template<> class Collection<fraction> : detail::CollectionBase<fraction>
{
public:
    void ArrayGenerating() {
        Base::Arr = new fraction[Base::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);
        }
    }
private:
    using Base = detail::CollectionBase<fraction>;
};

Generally, read more about template specialisation and you will certainly find the right solution:

Upvotes: 0

Ivaylo Valchev
Ivaylo Valchev

Reputation: 10425

Why would you want to do this and make your life infinitely more difficult? It could be as simple as this:

#include <iostream>
#include <chrono>
#include <random>

template<class type_t, std::size_t size>
class Array
{
private:
    type_t arr[size];
public:
    Array()
    {
        for (std::size_t i = 0; i < size; ++i)
        {
            //nice C++ random number generation
            auto seed = static_cast<unsigned>(std::chrono::system_clock::now().time_since_epoch().count());
            std::minstd_rand0 randm(seed);

            arr[i] = randm();
        }
    }

    //test function
    void print()
    {
        for (int i = 0; i < size; ++i)
            std::cout << arr[i] << " ";
    }
};

int main() 
{
    Array<int, 4> arr;
    arr.print();

    std::cin.get();
}

Try to get away from C-style C++. Join the dark side.

Upvotes: 0

Related Questions