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