std:: versus ::std::
再再再补充一个重载决议的例子。
大家可能在某些地方见过 ::std::
这样的代码,比如 ::std::swap
,::std::vector
,::std::nullptr_t
。
在 Qualified Name Lookup 一节的子节 Namespace Member Lookup 已经介绍,名称前面以 ::
修饰表示在全局作用域下查找。
一个例子:
namespace A
{
namespace B
{
void f() {
std::cout << "A::B::f()\n";
}
}
}
namespace B
{
void f() {
std::cout << "B::f()\n";
}
}
namespace A
{
void h() {
::B::f(); // B::f()
B::f(); // A::B::f()
}
}
若是将 B
为 std
会发生什么呢?
namespace A
{
namespace std
{
void f() {
// Using ::std:: ensures that you're referencing the correct name.
::std::cout << "A::std::f()\n";
}
}
}
namespace std
{
void f() {
std::cout << "std::f()\n";
}
}
namespace A
{
void h() {
::std::f(); // std::f()
std::f(); // A::std::f()
}
}
若是再使用标准组件,你必须以 ::std::
保证调用的是标准中的版本,否则将在当前命名空间下查找。
C++ 标准并不建议直接修改 namespace std 下面的内容,改一下而乱全身,普通用户并不会有这种烦恼,也几乎不会遇到必须 使用 ::std::
的情况。但你若是某个提案的作者,需要往标准里面添加新的东西,即使是在 namespace std 下,他们也会使用 ::std::
。
看起来似乎多此一举,因为你已经在 namespace std 下面,引用的肯定是 std::
下面的组件。
namespace std
{
// A standard function
void f() {
std::cout << "std::f()\n";
}
void h() {
std::f(); // std::f()
}
}
但这样可能会存在某些潜在冲突,比如某人在引用你之前添加了如下代码。
namespace std
{
namespace std
{
void f() {
::std::cout << "std::std::f()\n";
}
}
}
namespace std
{
// 标准当中的函数
void f() {
::std::cout << "std::f()\n";
}
void h() {
std::f(); // std::std::f()
}
}
不论是有意还是无意,你引用的标准内容被成功替换,用户可能还在报怨为什么我的代码行为不如意。
因此,即使在 namespace std 下面,使用 ::std::
不仅可以明确表示引用标准库内容,而且可以预防某些潜在的冲突。
再提供一个真实的例子,来自前几天写的一篇文章 借助 ChatGPT 快速实现一个轻量级的控制台进度条库。
struct fill {
char value;
int width;
};
template <>
struct std::formatter<fill> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const fill& f, auto& ctx) const {
return std::fill_n(ctx.out(), f.width, f.value);
}
};
乍看之下,这个实现没有丝毫问题。它的潜在问题就来源于 std::formatter
的定制方式,里面的所有名称都将默认在 std::
下面查找,而 std::fill
在标准中已经存在,因此解析出错。但是,报错并不会这么明显地告诉你问题在哪里,你可能得花费许多时间来排查这个潜在错误。
此时,一种解决办法就如当前所说,将 fill
放进你的命名空间之下。若你不想这样做,另一种更好的做法就是采用 ::
访问。
struct fill {
char value;
int width;
};
template <>
struct std::formatter<::fill> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const ::fill& f, auto& ctx) const {
return ::std::fill_n(ctx.out(), f.width, f.value);
}
};
int main() {
// =====
std::cout << std::format("{}", fill('=', 5));
}
如此,问题便不复存在。