Generic Array with a generic size C++ - c++

this is my generic class Array, I would like to ask about the copy constructor and assignment, is this the right way to do it? If yes, then do I really need to insert both of them in the class?
Thank you in advance.
template <class T, int SIZE>
class Array {
T data[SIZE];
public:
explicit Array();
Array(const Array& a); //copy constructor
~Array(); //destructor
Array& operator=(const Array& a); //assignment
T& operator[](int index);
const T& operator[](int index) const;
};
template <class T,int SIZE>
Array<T,SIZE>::Array()
{
}
template <class T,int SIZE>
Array<T,SIZE>::~Array()
{
}
template <class T,int SIZE>
Array<T,SIZE>::Array& operator=(const Array& a)
{
for(int i=0;i<SIZE;i++)
{
data[i]=a[i];
}
return *this;
}
template <class T,int SIZE>
Array<T,SIZE>::Array(const Array& a)
{
for(int i=0;i<SIZE;i++)
{
data[i]=a[i];
}
}

In this case, you can apply the Rule of Zero:
template <class T, int SIZE>
class Array {
T data[SIZE];
public:
T& operator[](int index);
const T& operator[](int index) const;
};
The compiler will generate the functions for you. If you need to do something custom in them, then yes, you need to define them.
In that case, the signature of your assignment operator needs to be fixed to (i.e. your posted code does not compile):
template <class T,int SIZE>
Array<T,SIZE>& Array<T,SIZE>::operator=(const Array& a)
Then, you should be directly accessing the other array's data, instead of using its operator[] (unless you have a reason):
data[i] = a.data[i]; // note `a.data[i]` vs. `a[i]`
Moreover, you can take advantage of the compiler to avoid writing loops. Simply wrap your array into a struct:
template <class T, int SIZE>
class Array {
struct S {
T data[SIZE];
} s;
// ...
};
So that you can replace your loops with:
s = a.s;
Not only that, but using the struct will allow you to copy-construct the elements of the array, rather than copy-assigning them (as you were doing with the loop), which may be important for some T types:
template <class T,int SIZE>
Array<T,SIZE>::Array(const Array& a)
: s(a.s) // note the member initializer
{
// empty body
}

This solves the compiler error:
template <class T,int SIZE>
auto Array<T,SIZE>::operator=(const Array& a) -> Array&
{
for(int i=0;i<SIZE;i++)
{
data[i]=a[i];
}
return *this;
}
There is a separate discussion to be had about redundant initialisation of the array elements in the constructor.

Related

C++ vector constructor implementation

How do I make it possible to call the following construct for my vector class?
vector <int> v{1,2,3};
how to properly declare such a method?
My source code:
template<typename T>
class my_vector {
T* values;
std::size_t values_num;
std::size_t max_size;
public:
explicit my_vector(std::size_t size);
~my_vector();
void push_back(const T& value);
std::size_t size();
T operator[](int pos);
T at(int pos);
};
The easy way is a constructor taking a std::initializer_list<T>.
Another way is writing a template constructor, a bit like this:
template<class...Ts> requires (std::is_same_v<T, Ts> && ...)
explicit my_vector(Ts...ts);
but getting that just right is a pain, so just go with initializer_list.

When to write the template angle brackets (<...>)?

Given this example class template:
template<typename T>
class Stack {
T * data;
int size;
int nextIndex;
public:
Stack(int size = 100);
Stack(const Stack& stack);
~Stack();
Stack& operator=(const Stack& s);
void push(const T& t);
void pop();
T& top();
const T& top() const;
int getSize() const;
class Full {
};
class Empty {
};
};
template<typename T>
void Stack::push(const T& t) {
if (nextIndex >= size) {
throw Full();
}
data[nextIndex++] = t;
}
 
template<typename T>
void Stack::pop() {
if (nextIndex <= 0) {
throw Empty();
}
nextIndex--;
}
Is it ok the part of the implementaion of the push and pop methods?
I don't understand if I need to write void Stack<T>::push(const T& t) instead of void Stack::push(const T& t) (and the same for the pop method).
NOTE: Eclipse (according to C++11) gives me the next error:
Member declaration not found
because of these lines:
void Stack::push(const T& t) {
void Stack::pop() {
The part of the implementaion of push method and pop method it's ok? I don't understand if I need to write void Stack::push(const T& t) instead void Stack::push(const T& t) (and the same for pop method).
You need to use
template <typename T>
void Stack<T>::push(const T& t) { ... }
template <typename T>
void Stack<T>::pop() { ... }
The name Stack is the same as Stack<T> inside the class template definition when it is used as a typename. Outside the class template definition, you have to supply the template parameter explicitly.
Both push() and pop() are (non-template) member functions of the Stack class template. Since this class template, Stack, is parameterized by a type template parameter (i.e.: T), so are those member functions as well.
Therefore, the implementation of those member functions needs a type template parameter:
template<typename A>
void Stack<A>::push(const A& t) { ... }
template<typename B>
void Stack<B>::pop() { ... }
Note that the name of the template parameter is actually irrelevant (A and B above).
Note as well that the name of the class template Stack not followed by any template arguments inside the body of its definition is equivalent to the class template with its template parameter as the template argument, i.e.: Stack<T>.

Removing a member based on a template parameter

I would like to design a vector class with small vector optimisation. It looks like:
template <typename T, int small_size = 0>
class Vector {
private:
T data_small_[small_size];
T* data_;
T* size_;
T* capacity_;
public:
...
}
Unfortunately, most of the time, the class will be used with small_size = 0. Is there a way to remove data_small_ for small_size = 0 without going into template specialisation and rewriting the whole code for the class?
You can use the empty base optimization here. You would have to change Vector to not refer to data_small_ directly. Instead, write use SmallData to implement the required member functions and handle the small_size = 0 case in the partial specialization SmallData<T, 0>. Below is an example with SmallData implementing a copy constructor and operator[] and Vector using them without worrying about small_size.
template <typename T, int small_size>
class SmallData
{
public:
SmallData(const SmallData& other)
{
for(size_t i = 0; i < small_size; i++)
data_small_[i] = other.data_small_[i];
}
T& operator[](int k){return data_small_[k];}
protected:
T data_small_[small_size];
};
template <typename T>
class SmallData<T, 0>
{
public:
T& operator[](int k){//throw some error}
};
template <typename T, int small_size = 0>
class Vector : public SmallData<T, small_size>
{
public:
Vector(const Vector& other) : SmallData<T, small_size>(other)
{
//rest of copy ctor either here on in the member init list above
}
T& operator[](int k)
{
if(k<small_size) return SmallData<T, small_size>::operator[](k);
else return data_[k];
}
};

Overloading Sum Operator in Custom Vector Class

As an exercise to learn a little more about dynamic memory allocation in C++, I'm working on my own vector class. I'm running into a little difficulty overloading the sum operator, so I thought I'd turn here to get some insight into why this doesn't work. Here's what I have so far:
template<typename T>
class vector
{
private:
T* pointer_;
unsigned long size_;
public:
// Constructors and destructors.
vector();
template<typename A> vector(const A&);
template<typename A> vector(const A&, const T&);
vector(const vector<T>&);
~vector();
// Methods.
unsigned long size();
T* begin();
T* end();
vector<T>& push_back(const T&);
// Operators.
T operator[](unsigned long);
vector<T>& operator=(const vector<T>&);
friend vector<T>& operator+(const vector<T>&, const vector<T>&);
};
The template<typename A> vector(const A&) constructor looks like this:
template<typename T> template<typename A>
vector<T>::vector(const A& size)
{
this->pointer_ = new T[size];
this->size_ = size;
}
Finally, the operator+ operator looks like this:
template<typename T>
vector<T>& operator+(const vector<T>& lhs, const vector<T>& rhs)
{
vector<T> result(lhs.size_);
for (unsigned long i = 0; i != result.size_; i++)
{
result.pointer_[i] = lhs.pointer_[i] + rhs.pointer_[i];
}
return result;
}
My compiler (VS2013) returns an unresolved external symbol error when I try to compile this code, which (as I understand it) means there's a function declared somewhere that I haven't actually defined. I'm not sure where the problem is, however: the template<typename A> vector(const A&) constructor works fine. What am I missing?
You're not properly friending the template operator function. There are multiple ways to do this, each of which has advantages. One is the friend-the-world ideology where all expansions of the template are friends with each other. I prefer to be a bit more restrictive than that.
Above your vector class, do this:
template<typename T>
class vector;
template<typename T>
vector<T> operator+(const vector<T>& lhs, const vector<T>& rhs);
The proceed as before, but note the syntax in the friend declaration:
// vector class goes here....
template<typename T> class vector
{
.... stuff ....
friend vector<T> operator+<>(const vector<T>&, const vector<T>&);
};
Then define the rest as you have it. That should get you what I think you're trying to achieve.
Best of luck.
PS: invalid reference return value fixed in the above code.

Overloading class' operator [] in C++

I have a class which I have written its [] operator, and I want that sometimes the operator will return an int and sometimes a struct.
But the compiler won't let me overload the operator, why?
It says:"...cannot be overloaded"
Code:
template <class T> struct part
{ };
template <class T> class LinkedList
{
public:
LinkedList() : size(0), head(0) {}
T& operator[](const int &loc);
part<T>& operator[](const int &loc);
};
template <class T> T& LinkedList<T>::operator[](const int &loc)
{
..a lot of thing which compiles perfectly
}
template <class T> part<T>& LinkedList<T>::operator[](const int &loc)
{
...the same thing but returns struct&.
}
You can't overload a function based on the return type. You could have your operator return a variant of int and string, and let the user check which was actually returned, but its cumbersome. If the return type can be determined at compilation time, you can implement the operator overloads by mean of having different indices types. Something like this:
struct as_string
{
as_string( std::size_t index ) : _index( index ){}
std::size_t const _index;
};
...
int operator[]( int index ) const { ... };
std::string operator[]( as_string const& index ) const { ... };
And then the caller would invoke object[0] to get an int result, or object[as_string(0)] to get a string result.
The type of a function's output is not a part of the function's signature. Thus you can't use both int operator[](int index) and Foo operator[](int index).