您的代码有正确的想法,但缺少一些东西。
template <typename T>
ostream& operator<<(ostream& os, T something)
{
os << something.begin() << something.end();
return os;
}
可迭代容器(如std::map 等)应通过迭代其所有元素并逐个输出来输出。在这里,您只输出开始和结束 迭代器,它们与元素本身不同。
我们可以改为使用*it 从容器中的迭代器中获取元素。因此,下面的代码将在T 类型的标准容器中输出所有元素。我还包括一些额外的漂亮印刷。
template <typename T>
std::ostream &operator<<(std::ostream &os, const T &o) {
auto it = o.begin();
os << "{" << *it;
for (it++; it != o.end(); it++) {
os << ", " << *it;
}
return os << "}";
}
如果我们只是使用
template <typename T>
在此函数声明之前,它将与现有的<< 运算符声明冲突。也就是我们写std::cout << std::string("hello world");的时候,是调用我们的函数实现,还是调用<string>的函数实现?当然,如果可用,我们希望使用标准的operator<< 实现。我们通过限制模板来做到这一点,使其仅适用于具有begin() 和end() 成员的标准容器,但不适用于具有begin() 和end() 但也具有现有operator<< 实现的std::string我们想要使用的。
template <typename T,
typename std::enable_if<is_iterable<T>::value, bool>::type = 0,
typename std::enable_if<!std::is_same<T, std::string>::value, bool>::type = 0>
第二个std::enable_if 很简单:模板应该涵盖类型,只要它们不是std::string。第一个std::enable_if 检查T 类型是否是可迭代的。我们需要自己进行检查。
template <typename T>
class is_iterable {
private:
typedef char True[1];
typedef char False[2];
template <typename Q,
typename std::enable_if<
std::is_same<decltype(std::declval<const Q &>().begin()),
decltype(std::declval<const Q &>().begin())>::value,
char>::type = 0>
static True &test(char);
template <typename...>
static False &test(...);
public:
static bool const value = sizeof(test<T>(0)) == sizeof(True);
};
is_iterable 有两个版本的函数test。如果begin() 和end() 存在于类型T 上,则启用第一个版本,并且它们的返回类型相同(有更精确的方法进行检查,但现在就足够了)。第二个版本以其他方式调用。两个版本的返回类型不同,通过查看返回类型的大小,我们可以设置value,当且仅当T是iterable时,true(在在我们的例子中,如果T 定义了begin() 和end() 并且它们的返回类型相同)。
最后,我们注意到std::map<T1, T2> 的元素实际上是std::pair<T1, T2> 类型的,所以我们需要额外重载operator<< 用于模板对。
template <typename T1, typename T2>
std::ostream &operator<<(std::ostream &os, const std::pair<T1, T2> &o) {
return os << "(" << o.first << ", " << o.second << ")";
}
综合起来,我们可以试试这个。请注意,它甚至适用于嵌套的 iterator 类型,例如 listUnorderedSetTest。
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <type_traits>
#include <unordered_set>
#include <vector>
template <typename T>
class is_iterable {
private:
typedef char True[1];
typedef char False[2];
template <typename Q,
typename std::enable_if<
std::is_same<decltype(std::declval<const Q &>().begin()),
decltype(std::declval<const Q &>().begin())>::value,
char>::type = 0>
static True &test(char);
template <typename...>
static False &test(...);
public:
static bool const value = sizeof(test<T>(0)) == sizeof(True);
};
template <typename T1, typename T2>
std::ostream &operator<<(std::ostream &os, const std::pair<T1, T2> &o) {
return os << "(" << o.first << ", " << o.second << ")";
}
template <typename T,
typename std::enable_if<is_iterable<T>::value, bool>::type = 0,
typename std::enable_if<!std::is_same<T, std::string>::value, bool>::type = 0>
std::ostream &operator<<(std::ostream &os, const T &o) {
auto it = o.begin();
os << "{" << *it;
for (it++; it != o.end(); it++) {
os << ", " << *it;
}
return os << "}";
}
int main() {
std::vector<std::string> vectorTest{"hello", "world", "!"};
std::cout << vectorTest << std::endl;
std::set<const char *> setTest{"does", "this", "set", "work", "?"};
std::cout << setTest << std::endl;
std::map<std::string, std::size_t> mapTest{
{"bob", 100}, {"alice", 16384}, {"xavier", 216}};
std::cout << mapTest << std::endl;
std::list<std::unordered_set<std::string>> listUnorderedSetTest{
{"alice", "abraham", "aria"},
{"carl", "crystal", "ciri"},
{"november", "nathaniel"}};
std::cout << listUnorderedSetTest << std::endl;
return 0;
}
这个输出:
{hello, world, !}
{does, this, set, work, ?}
{(alice, 16384), (bob, 100), (xavier, 216)}
{{alice, abraham, aria}, {carl, crystal, ciri}, {november, nathaniel}}
Templated check for the existence of a class member function? 上还有很多其他相关的讨论,您可能会觉得有帮助。这个答案的缺点是检查std::string,而不是检查现有的operator<< 实现,我认为可以通过更多的工作来解决decltype 的类型检查。