Simple array implementation without bounds-checking
In the constructor that just takes a size. I would initialize all members to their default value even if POD.

template &lt;class T&gt;
Array&lt;T&gt;::Array(const int&amp; init_size)
{
  arr = new T[init_size]();
                  //    ^^^^^  Add braces. on types it makes no difference.
                  //           On POD it will force zero initialization.
  size = init_size;
}


Use the initializer lists in your constructors.

template &lt;class T&gt;
Array&lt;T&gt;::Array(const int&amp; init_size)
    : arr(new T[init_size]())
    , size(init_size)
{}


Use copy and swap idiom for assignment operator (it automatically provides the strong exception guarantee).

template &lt;class T&gt;
Array&lt;T&gt;&amp; Array&lt;T&gt;::operator= (const Array&amp; copy)
{
    if (arr == copy.arr)  // This is the wrong test.
        return *this;     // Though it works as a side effect you will confuse the maintainer.

    size = copy.size;
    delete [] arr;       // breaks the strong exception gurantee.
                         // What it the next line fails? Then you are left with an object
                         // in an inconsistent state. (arr points at de-allocated memory)
    arr = new T[size];

    for (int i = 0; i &lt; size; ++i)
    {
        arr[i] = copy.arr[i];
    }
    return *this;
}


If you want to do this the hard way then it should look like this:

template &lt;class T&gt;
Array&lt;T&gt;&amp; Array&lt;T&gt;::operator= (const Array&amp; copy)
{
    if (this == &amp;copy)    // Return quickly on assignment to self.
    {   return *this;
    }

    // Do all operations that can generate an expception first.
    // BUT DO NOT MODIFY THE OBJECT at this stage.
    T* tmp = new T[size];
    for (int i = 0; i &lt; size; ++i)
    {
        tmp[i] = copy.arr[i];
    }

    // Now that you have finished all the dangerous work.
    // Do the operations that  change the object.
    std::swap(tmp, arr);
    size = copy.size;

    // Finally tidy up
    delete tmp;    // Notice the swap above.

    // Now you can return
    return *this;
}


Alternatively use the copy and swap idiom

template &lt;class T&gt;
Array&lt;T&gt;&amp; Array&lt;T&gt;::operator=(Array const&amp; rhs)
{
    // 1 Copy
    Array    copy(rhs);     // Use the copy constructor to make a safe copy.

    // 2 Swap
    swap(arr,   copy.arr);
    swap(size,  copy.size);

} // 3 as the local copy goes out of scope it destroys the old array.


Note this can be optimized too:

Array&lt;T&gt;&amp; Array&lt;T&gt;::operator=(Array rhs) // 1 implicit copy as parameter is passed by value
{
    // 2 Swap
    swap(arr,   copy.arr);
    swap(size,  copy.size);

} // 3 as the local copy goes out of scope it destroys the old array.


You don't need a get/set methods. Infact get/set is an obvious indication of bad design.

template &lt;class T&gt;
T Array&lt;T&gt;::getVal(const int&amp; index) const {
  return arr[index];
}

template &lt;class T&gt;
void Array&lt;T&gt;::setVal(const int&amp; index, const T&amp; value) {
  arr[index] = value;
}


Just remove both these methods. What you want to do is return a reference to the internal object in your overload of operator[] (which you do) bit the index is an integer usually.

template &lt;class T&gt;
T&amp; Array&lt;T&gt;::operator[] (size_t index)
{                //      ^^^^^^^    Changed type here.
  return arr[index];
}


You also need to provide a verision to use when your array is const.

template &lt;class T&gt;
T const&amp; Array&lt;T&gt;::operator[] const (size_t index)
//^^^^^   Here                ^^^^^  and here.
{
  return arr[index];
}


If you provide an output operator you should generally by symmetric and provide an input operator.