<< 运算符模板的实现 // C++
Implementation of template of a << operator // C++
我想在 C++ 中制作一个 << 运算符的模板,它将显示一个“范围”的对象(我的意思是任何对象,如:std::vector、std::set, std::map, std::deque).我怎样才能做到这一点?几天来我一直在谷歌搜索和查看文档,但没有任何效果。我之前一直在做一些模板并且覆盖了一些运算符,但这些都在某个 class 中,它代表一个自定义向量 class。我似乎找不到实现它的好方法,因为它与标准 cout 冲突。那么我该怎么做,在 class 内部可以传递向量、集合、映射、双端队列作为参数,并在内部传递运算符?我还希望此运算符 return 对象的 begin() 和 end() 迭代器。现在我有这个代码:
template <typename T>
ostream& operator<<(ostream& os, T something)
{
os << something.begin() << something.end();
return os;
}
它真的不起作用,我认为有经验的 C++ 程序员可以解释为什么。
在此先感谢您对该问题的任何回答。
您的重载将匹配几乎所有导致 operator<<
已经具有重载的类型的歧义。
我怀疑您想在这里打印容器中的所有元素:os << something.begin() << something.end();
。这将不起作用,因为 begin()
和 end()
return 迭代器。您可以取消引用它们
if(something.begin() != something.end())
os << *something.begin() << *std::prev(something.end());
但您只会打印第一个和最后一个元素。这将打印所有这些:
for(const auto& v : something) os << v;
要解决歧义问题,您可以使用模板模板参数并为您想要支持的容器启用 operator<<
重载。
示例:
#include <deque>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <type_traits>
#include <vector>
// helper trait - add containers you'd like to support to the list
template <typename T> struct is_container : std::false_type {};
template <typename... Ts> struct is_container<std::vector<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::list<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::deque<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::map<Ts...>> : std::true_type{};
// C is the container template, like std::vector
// Ts... are the template parameters used to create the container.
template <template <typename...> class C, typename... Ts>
// only enable this for the containers you want to support
typename std::enable_if<is_container<C<Ts...>>::value, std::ostream&>::type
operator<<(std::ostream& os, const C<Ts...>& something) {
auto it = something.begin();
auto end = something.end();
if(it != end) {
os << *it;
for(++it; it != end; ++it) {
os << ',' << *it;
}
}
return os;
}
另一种方法是使其成为通用的,但对已经支持流的类型禁用重载。
#include <iostream>
#include <iterator>
#include <type_traits>
// A helper trait to check if the type already supports streaming to avoid adding
// an overload for std::string, std::filesystem::path etc.
template<typename T>
class is_streamable {
template<typename TT>
static auto test(int) ->
decltype( std::declval<std::ostream&>() << std::declval<TT>(), std::true_type() );
template<typename>
static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T,
typename U = decltype(*std::begin(std::declval<T>())), // must have begin
typename V = decltype(*std::end(std::declval<T>())) // must have end
>
// Only enable the overload for types not already streamable
typename std::enable_if<not is_streamable<T>::value, std::ostream&>::type
operator<<(std::ostream& os, const T& something) {
auto it = std::begin(something);
auto end = std::end(something);
if(it != end) {
os << *it;
for(++it; it != end; ++it) {
os << ',' << *it;
}
}
return os;
}
注意:最后一个示例在 clang++
和 MSVC
中有效,但在 g++
中无法编译(超出递归深度)。
对于具有 value_type
本身不可流式传输的容器,例如 std::map
中的 std::pair<const Key, T>
,您需要添加单独的重载。这需要在 之前 任何上述模板声明:
template <typename Key, typename T>
std::ostream &operator<<(std::ostream &os, const std::pair<const Key, T>& p) {
return os << p.first << ',' << p.second;
}
您的代码思路正确,但缺少一些东西。
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()
的 std::string
] 但也有我们要使用的现有 operator<<
实现。
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
上,并且它们的 return 类型相同,则启用第一个版本(有更精确的方法来进行检查,但这就足够了目前)。第二个版本以其他方式调用。两个版本的return类型不同,通过检查return类型的大小,我们可以设置value
,当且仅当T
是 iterable (在我们的例子中,如果 T
定义了 begin()
和 end()
并且它们的 return 类型是相同的) .
最后,我们注意到 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
.[= 进行类型检查来解决这个问题。 54=]
我想在 C++ 中制作一个 << 运算符的模板,它将显示一个“范围”的对象(我的意思是任何对象,如:std::vector、std::set, std::map, std::deque).我怎样才能做到这一点?几天来我一直在谷歌搜索和查看文档,但没有任何效果。我之前一直在做一些模板并且覆盖了一些运算符,但这些都在某个 class 中,它代表一个自定义向量 class。我似乎找不到实现它的好方法,因为它与标准 cout 冲突。那么我该怎么做,在 class 内部可以传递向量、集合、映射、双端队列作为参数,并在内部传递运算符?我还希望此运算符 return 对象的 begin() 和 end() 迭代器。现在我有这个代码:
template <typename T>
ostream& operator<<(ostream& os, T something)
{
os << something.begin() << something.end();
return os;
}
它真的不起作用,我认为有经验的 C++ 程序员可以解释为什么。
在此先感谢您对该问题的任何回答。
您的重载将匹配几乎所有导致 operator<<
已经具有重载的类型的歧义。
我怀疑您想在这里打印容器中的所有元素:os << something.begin() << something.end();
。这将不起作用,因为 begin()
和 end()
return 迭代器。您可以取消引用它们
if(something.begin() != something.end())
os << *something.begin() << *std::prev(something.end());
但您只会打印第一个和最后一个元素。这将打印所有这些:
for(const auto& v : something) os << v;
要解决歧义问题,您可以使用模板模板参数并为您想要支持的容器启用 operator<<
重载。
示例:
#include <deque>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <type_traits>
#include <vector>
// helper trait - add containers you'd like to support to the list
template <typename T> struct is_container : std::false_type {};
template <typename... Ts> struct is_container<std::vector<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::list<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::deque<Ts...>> : std::true_type{};
template <typename... Ts> struct is_container<std::map<Ts...>> : std::true_type{};
// C is the container template, like std::vector
// Ts... are the template parameters used to create the container.
template <template <typename...> class C, typename... Ts>
// only enable this for the containers you want to support
typename std::enable_if<is_container<C<Ts...>>::value, std::ostream&>::type
operator<<(std::ostream& os, const C<Ts...>& something) {
auto it = something.begin();
auto end = something.end();
if(it != end) {
os << *it;
for(++it; it != end; ++it) {
os << ',' << *it;
}
}
return os;
}
另一种方法是使其成为通用的,但对已经支持流的类型禁用重载。
#include <iostream>
#include <iterator>
#include <type_traits>
// A helper trait to check if the type already supports streaming to avoid adding
// an overload for std::string, std::filesystem::path etc.
template<typename T>
class is_streamable {
template<typename TT>
static auto test(int) ->
decltype( std::declval<std::ostream&>() << std::declval<TT>(), std::true_type() );
template<typename>
static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T,
typename U = decltype(*std::begin(std::declval<T>())), // must have begin
typename V = decltype(*std::end(std::declval<T>())) // must have end
>
// Only enable the overload for types not already streamable
typename std::enable_if<not is_streamable<T>::value, std::ostream&>::type
operator<<(std::ostream& os, const T& something) {
auto it = std::begin(something);
auto end = std::end(something);
if(it != end) {
os << *it;
for(++it; it != end; ++it) {
os << ',' << *it;
}
}
return os;
}
注意:最后一个示例在 clang++
和 MSVC
中有效,但在 g++
中无法编译(超出递归深度)。
对于具有 value_type
本身不可流式传输的容器,例如 std::map
中的 std::pair<const Key, T>
,您需要添加单独的重载。这需要在 之前 任何上述模板声明:
template <typename Key, typename T>
std::ostream &operator<<(std::ostream &os, const std::pair<const Key, T>& p) {
return os << p.first << ',' << p.second;
}
您的代码思路正确,但缺少一些东西。
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()
的 std::string
] 但也有我们要使用的现有 operator<<
实现。
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
上,并且它们的 return 类型相同,则启用第一个版本(有更精确的方法来进行检查,但这就足够了目前)。第二个版本以其他方式调用。两个版本的return类型不同,通过检查return类型的大小,我们可以设置value
,当且仅当T
是 iterable (在我们的例子中,如果 T
定义了 begin()
和 end()
并且它们的 return 类型是相同的) .
最后,我们注意到 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
.[= 进行类型检查来解决这个问题。 54=]