std:: versus ::std::

教育   科技   2023-12-30 22:42   美国  

再再再补充一点重载决议的内容。

大家可能在某些地方见过 ::std:: 这样的代码,比如 ::std::swap::std::vector::std::nullptr_t

在 Qualified Name Lookup 一节的子节 Namespace Member Lookup 已经介绍,名称前面以 :: 修饰表示在全局作用域下查找。

一个例子:

 1namespace A
2{
3    namespace B
4    {
5        void f() {
6            std::cout << "A::B::f()\n";
7        }
8    }
9}
10namespace B
11{
12    void f() {
13        std::cout << "B::f()\n";
14    }
15}
16namespace A
17{
18    void h() {
19        ::B::f(); // B::f()
20        B::f();   // A::B::f()
21    }
22}

若是将 Bstd 会发生什么呢?

 1namespace A
2{
3    namespace std
4    {
5        void f() {
6            // Using ::std:: ensures that you're referencing the correct name.
7            ::std::cout << "A::std::f()\n";
8        }
9    }
10}
11namespace std
12{
13    void f() {
14        std::cout << "std::f()\n";
15    }
16}
17
18namespace A
19{
20    void h() {
21        ::std::f(); // std::f()
22        std::f();   // A::std::f()
23    }
24}

若是再使用标准组件,你必须以 ::std:: 保证调用的是标准中的版本,否则将在当前命名空间下查找。

C++ 标准并不建议直接修改 namespace std 下面的内容,改一下而乱全身,普通用户并不会有这种烦恼,也几乎不会遇到必须使用 ::std:: 的情况。但若是某个提案的作者,需要往标准里面添加新的东西,即使是在 namespace std 下,他们也会使用 ::std::

看起来似乎多此一举,因为你已经在 namespace std 下面,引用的肯定是 std:: 下面的组件。

 1namespace std
2{
3    // A standard function
4    void f() {
5        std::cout << "std::f()\n";
6    }
7
8    void h() {
9        std::f(); // std::f()
10    }
11}

但这样可能会存在某些潜在冲突,比如某人在引用你之前使用添加了如下代码。

 1namespace std
2{
3    namespace std
4    {
5        void f() {
6            ::std::cout << "std::std::f()\n";
7        }
8    }
9}
10
11namespace std
12{
13    // A standard function
14    void f() {
15        ::std::cout << "std::f()\n";
16    }
17
18    void h() {
19        std::f(); // std::std::f()
20    }
21}

不论是有意还是无意,你引用的标准内容被成功替换,用户可能还在报怨为什么代码行为如此出乎意料。

因此,即使在 namespace std 下面,使用 ::std:: 不仅可以明确表示引用标准库内容,而且可以预防某些潜在的冲突。

再提供一个真实的例子,来自前几天写的一篇文章借助 ChatGPT 快速实现一个轻量级的控制台进度条库

 1struct fill {
2    char value;
3    int width;
4};
5
6template <>
7struct std::formatter<fill> {
8  constexpr auto parse(format_parse_context& ctx) return ctx.begin(); }
9
10  auto format(const fill& f, auto& ctx) const {
11    return std::fill_n(ctx.out(), f.width, f.value);
12  }
13};

乍看之下,这个实现没有丝毫问题。

它的潜在问题就来源于 std::formatter 的定制方式,里面的所有名称都将默认在 std:: 下面查找,而 std::fill 在标准中已经存在,于是解析出错。但是,报错并不会这么明显地告诉你问题在哪里,你可能得花费许多时间来排查这个潜在错误。

此时,一种解决办法就如当时所说,将 fill 放进你的命名空间之下。若你不想这样做,另一种更好的做法就是采用 :: 访问。

 1struct fill {
2    char value;
3    int width;
4};
5
6template <>
7struct std::formatter<::fill> {
8  constexpr auto parse(format_parse_context& ctx) return ctx.begin(); }
9
10  auto format(const ::fill& f, auto& ctx) const {
11    return ::std::fill_n(ctx.out(), f.width, f.value);
12  }
13};
14
15
16int main() {
17    // =====
18    std::cout << std::format("{}", fill('='5));
19}

如此,问题便不复存在。


CppMore
Dive deep into the C++ core, and discover more!
 最新文章