C++11
template <typename T, typename = typename std::enable_if< std::is_base_of<Base1, T>::value || std::is_base_of<Base2, T>::value>::type> std::ostream &operator<<(std::ostream &os, const T &t) { // os << t.member1 << t.member2...; return os; }
or:
template <typename T, typename std::enable_if< std::is_base_of<Base1, T>::value || std::is_base_of<Base2, T>::value>::type * = nullptr> std::ostream &operator<<(std::ostream &os, const T &t) { // os << t.member1 << t.member2...; return os; }
The latter approach can be useful when wanting to provide mutually exclusive SFINAE-constrained overloads that differ only in their SFINAE predicates. The former approach is not viable in that case as two template functions differing only in their default template arguments declare the same function template, as default template arguments are not part of a function template's signature.
C++14 (using the std::enable_if_t utility alias template)
template <typename T, typename = std::enable_if_t<std::is_base_of<Base1, T>::value || std::is_base_of<Base2, T>::value>> std::ostream &operator<<(std::ostream &os, const T &t) { // os << t.member1 << t.member2...; return os; }
C++17 (using the _v utility variable templates)
template <typename T, typename = std::enable_if_t<std::is_base_of_v<Base1, T> || std::is_base_of_v<Base2, T>>> std::ostream &operator<<(std::ostream &os, const T &t) { // os << t.member1 << t.member2...; return os; }