You could do this:
template <typename T, unsigned int N>
std::ostream & operator<<(std::ostream & os, const T (&arr)[N])
{
  // ..
  return os;
}
This works only for compile-time arrays, of course. Note that you are not allowed to instantiate this template when T is a built-in type or a type in the std namespace!
Probably best to make this inline if possible, since you'll cause a separate instantiation for every N. (The pretty printer has an example of this.)
You will notice, though, that the blanket template introduces an ambiguity, because os << "Hello" now has two possible overloads: the template matching const char (&)[6], and the (non-template) overload for the decay-to-pointer const char *, which both have identical conversion sequences. We can resolve this by disabling our overload for char arrays:
#include <ostream>
#include <type_traits>
template <typename T, unsigned int N>
typename std::enable_if<!std::is_same<T, char>::value, std::ostream &>::type
operator<<(std::ostream & os, const T (&arr)[N])
{
  // ..
  return os;
}
In fact, to be even more general you can also make the basic_ostream parameters template parameters:
template <typename T, unsigned int N, typename CTy, typename CTr>
typename std::enable_if<!std::is_same<T, char>::value,
                        std::basic_ostream<CTy, CTr> &>::type
operator<<(std::basic_ostream<CTy, CTr> & os, const T (&arr)[N])
{
  // ..
  return os;
}
In view of the fact that T must be a user-defined type, you could even replace is_same<T, char> with is_fundamental<T> to get a bit more checking (but users still must not use this for arrays of standard library types).