有一天你想对容器重载运算符, 但是却出现了神秘问题

在日复一日地对容器书写

for.cppview raw
1
2
3
4
for (auto it = container.begin(); it != container.end();) {
std::cout << *it++;
if (it != container.end()) std::cout << ", ";
}

后, 你终于受不了了, 于是你决定写一个重载来一劳永逸地解决这个问题

overload.cppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <set>
#include <vector>

template <class Container,
decltype(std::declval<Container>().begin(),
std::declval<Container>().end(),
0) * = nullptr>
std::ostream &operator<<(std::ostream &os, const Container &container) {
if (container.begin() == container.end()) return os << "[]";
os << '[';
for (auto it = container.begin(); it != container.end();) {
std::cout << *it++;
if (it != container.end()) std::cout << ", ";
}
os << ']';
return os;
}

int main() {
std::vector<int> v{1, 1, 4, 5, 1, 4};
std::cout << v << std::endl;
std::set<int> s{1, 1, 4, 5, 1, 4};
std::cout << s << std::endl;
return 0;
}

一切看起来都是那么美好, 直到你尝试输出一个字符串. 在你输出字符串时, 编译器拒绝了你的代码, 并说你的重载和

sign.cppview raw
1
2
3
4
template <class CharT, class Traits, class Allocator>
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> &,
const std::basic_string<CharT, Traits, Allocator> &);

撞车了

为什么会这样呢? 答案在于部分模板特化的 匹配规则, 简单来说, 编译器不能确定你的重载和 <string> 里的重载哪个更特殊, 画成 Hasse 图是这样的:

注意到你的 重载<string> 里的 重载 是不可比的, 所以在匹配时无法决定哪个优先级更高

因此正确的写法应该是这样:

overload2.cppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <set>
#include <vector>

template <class CharT,
class Traits,
class Container,
decltype(std::declval<Container>().begin(),
std::declval<Container>().end(),
0) * = nullptr>
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> &os, const Container &container) {
if (container.begin() == container.end()) return os << "[]";
os << '[';
for (auto it = container.begin(); it != container.end();) {
std::cout << *it++;
if (it != container.end()) std::cout << ", ";
}
os << ']';
return os;
}

int main() {
std::vector<int> v{1, 1, 4, 5, 1, 4};
std::cout << v << std::endl;
std::set<int> s{1, 1, 4, 5, 1, 4};
std::cout << s << std::endl;
std::string str = "114514";
std::cout << str << std::endl;
return 0;
}

参考资料