For those that are interested: I wrote a generalized solution that takes the best of both worlds, is more generalized to any type of range and puts quotes around non-arithmetic types (desired for string-like types). Additionally, this approach should not have any ADL issues and also avoid 'surprises' (since it's added explicitly on a case-by-case basis):
template <typename T>
inline constexpr bool is_string_type_v = std::is_convertible_v<const T&, std::string_view>;
template<class T>
struct range_out {
range_out(T& range) : r_(range) {
}
T& r_;
static_assert(!::is_string_type_v<T>, "strings and string-like types should use operator << directly");
};
template <typename T>
std::ostream& operator<< (std::ostream& out, range_out<T>& range) {
constexpr bool is_string_like = is_string_type_v<T::value_type>;
constexpr std::string_view sep{ is_string_like ? "', '" : ", " };
if (!range.r_.empty()) {
out << (is_string_like ? "['" : "[");
out << *range.r_.begin();
for (auto it = range.r_.begin() + 1; it != range.r_.end(); ++it) {
out << sep << *it;
}
out << (is_string_like ? "']" : "]");
}
else {
out << "[]";
}
return out;
}
Now it's fairly easy to use on any range:
std::cout << range_out{ my_vector };
The string-like check leaves room for improvement.
I do also have static_assert
check in my solution to avoid std::basic_string<>
, but I left it out here for simplicity.