Reputation: 519
I was originally going to use a template for an Array-style class and just pass it a char*
or an int
, but I ran into problems when trying to implement things like:
template<Typename T>
class Array {
void add(T elem) {
if(size == capacity) expandArr();
if(T == char*) //g++ threw errors here
arr[size] = new char[strlen(word) + 1];
strcpy(arr[size], word);
else if(T == int) {
arr[size] = elem;
}
size++;
}
}
Is there another way to check for the element type legally? Or should I just separate the two classes and have them be independent?
I was originally trying to use a templated class because both Array types have the same functionality, but there are some fundamental differences in how memory is allocated in this case for char*
's and int
's.
Upvotes: 1
Views: 119
Reputation: 545588
Class template specialisation is overkill in this case – it forces you to duplicate the whole class for different types even though ~90% of the functionality is going to be identical.
The key is to isolate those parts that differ and to specialise only those. For several reasons it’s easier to use overloading rather than specialisation, though.
The differing part in your case is only the assignment of a value to an array item.
template <typename T>
class Array {
void add(T elem) {
if(size == capacity) expandArr();
assignItem(elem);
size++;
}
template <typename U>
void assignItem(U elem) {
arr[size] = elem;
}
void assignItem(char* elem) {
// Incidentally, DON’T DO THIS! It leaks. Use RAII!
arr[size] = new char[strlen(elem) + 1];
strcpy(arr[size], word);
}
};
Of course now you’ve hard-coded the supported types of Array
: only types with a copy constructor and char*
are supported, no other types. In general you don’t want this restriction, you want to support arbitrary types and allow them to specify how they are copied1.
There are several ways of achieving this. I’ll just illustrate one here, via a trait class that specifies how a type is copied.
template <typename T>
struct CopyConstruct {
void assign(T& target, T source) const {
target = source;
}
};
template <typename T, typename Copy = CopyConstruct<T>>
class Array {
Copy copier;
void add(T elem) {
if(size == capacity) expandArr();
copier.assign(arr[size], elem);
size++;
}
};
This class can be used identically to your initial class. For char*
, the user has two choices: either specialise CopyConstruct
, or provide a completely own trait when instantiating Array
, like so:
struct CopyCString {
void assign(char*& target, char* source) const {
target = new char[strlen(elem) + 1];
strcpy(arr[size], word);
}
};
// And then:
Array<char*, CopyCString> strArray;
(And note that we’re passing a reference to a pointer here – otherwise the assigned memory would be lost because we’d assign the pointer to a copy of the array item, not the item itself.)
1 But in fact C++ already uses the copy constructor for exactly that purpose. The real solution isn’t any of the above, it’s to wrap char*
into a copyable type such as std::string
.
Upvotes: 2
Reputation: 14622
You should use template specialisation to have different behaviour for different template types.
Change your code to this:
template<typename T>
class Array {
void add(T elem) {
if(size == capacity) expandArr();
size++;
}
}
template<>
class Array<char*> {
void add(T elem) {
if(size == capacity) expandArr();
arr[size] = new char[strlen(word) + 1];
strcpy(arr[size], word);
size++;
}
}
template<>
class Array<int> {
void add(T elem) {
if(size == capacity) expandArr();
arr[size] = elem;
size++;
}
}
Upvotes: 2