std::literals::.. 作为内联命名空间有啥好处?

Posted

技术标签:

【中文标题】std::literals::.. 作为内联命名空间有啥好处?【英文标题】:What is the benefit of std::literals::.. being inline namespaces?std::literals::.. 作为内联命名空间有什么好处? 【发布时间】:2016-12-28 03:51:00 【问题描述】:

在 C++ 标准(例如 N4594)中,operator""s 有两个定义:

std::chrono::seconds 一个:

namespace std 
...
inline namespace literals 
inline namespace chrono_literals 
// 20.15.5.8, suffixes for duration literals
constexpr chrono::seconds operator "" s(unsigned long long);

还有一个std::string

namespace std  
....
inline namespace literals 
inline namespace string_literals 
// 21.3.5, suffix for basic_string literals:
string operator "" s(const char* str, size_t len);

我想知道从这些命名空间(以及 std::literals 中的所有其他命名空间)中获得了什么,如果它们是 inline

我认为它们位于不同的命名空间中,因此它们不会相互冲突。但是当他们是inline 时,这种动机就消失了,对吧? 编辑: 因为Bjarne explains 的主要动机是“库版本控制”,但这不适合这里。

我可以看到“秒”和“字符串”的重载是不同的,因此不会冲突。但是如果重载相同,它们会发生冲突吗?还是采取 (inline?) namespace 以某种方式阻止这种情况?

因此,他们在inline namespace 中获得了什么? 正如@Columbo 在下面指出的那样,跨内联命名空间的重载是如何解决的,它们是否会发生冲突?

【问题讨论】:

我明白了,您的问题是关于跨内联命名空间的重载。那不是一个重复。 @Columbo 确实如此。它也不是“什么是内联命名空间”的重复,因为这个答案是关于 versioning grm. 回复:unsiged long long,也许这是 unsigned ... 的拼写错误。 【参考方案1】:

用户定义的文字s 不会在secondsstring 之间“冲突”,即使它们都在范围内,因为它们像任何其他函数对一样在它们不同的参数列表上重载:

string  operator "" s(const char* str, size_t len);
seconds operator "" s(unsigned long long sec);

运行此测试可以证明这一点:

void test1()

    using namespace std;
    auto str = "text"s;
    auto sec = 1s;

对于using namespace std,两个后缀都在范围内,但不会相互冲突。

那么为什么要跳inline namespace 跳舞呢?

基本原理是允许程序员根据需要公开尽可能少的标准定义名称。在上面的测试中,我已将整个 std 库“导入”到 test,或者至少与 #included 一样多。

如果namespace literals 不是inlinetest1() 将无法工作。

这是使用文字的更受限制的方式,无需导入整个 std:

void test2()

    using namespace std::literals;
    auto str = "text"s;
    auto sec = 1s;
    string str2;  // error, string not declared.

这会引入所有标准定义的文字,但不会(例如)std::string

如果namespace string_literals 不是inline 并且namespace chrono_literals 不是inlinetest2() 将不起作用。

您也可以选择公开字符串文字,而不是计时文字:

void test3()

    using namespace std::string_literals;
    auto str = "text"s;
    auto sec = 1s;   // error

或者只是计时文字而不是字符串文字:

void test4()

    using namespace std::chrono_literals;
    auto str = "text"s;   // error
    auto sec = 1s;

最后有一种方法可以公开所有计时名称​​和计时文字:

void test5()

    using namespace std::chrono;
    auto str = "text"s;   // error
    auto sec = 1s;

test5() 需要这个魔法:

namespace chrono  // hoist the literals into namespace std::chrono
    using namespace literals::chrono_literals;

总而言之,inline namespaces 是一种工具,可让开发人员使用所有这些选项。

更新

OP 在下面提出了一些很好的后续问题。他们(希望)在本次更新中得到解决。

using namespace std 不是个好主意吗?

这取决于。 using namespace 在作为通用库一部分的标头中的全局范围内绝不是一个好主意。您不想将一堆标识符强制放入用户的全局命名空间中。该命名空间属于您的用户。

一个全局范围using namespace 可以在一个标头中使用,前提是该标头仅存在于您正在编写的应用程序中,并且如果您认为所有这些标识符都可用于包含该标头的所有内容,则您可以接受。但是,您转储到全局范围内的标识符越多,它们就越有可能与某些东西发生冲突。 using namespace std; 引入了 一堆 标识符,并且随着标准的每个新版本都将引入更多标识符。所以我不推荐 using namespace std; 在标头中的全局范围内,即使对于您自己的应用程序也是如此。

但是,我可以在标头的全局范围内看到 using namespace std::literalsusing namespace std::chrono_literals,但仅限于应用程序标头,而不是库标头。

我喜欢在函数范围内使用using 指令,因为标识符的导入仅限于函数范围。有了这样的限制,如果确实发生了冲突,解决起来就会容易得多。而且从一开始就不太可能发生。

std 定义的文字将可能不会相互冲突(他们今天不会)。但你永远不知道...

std 定义的文字将永远与用户定义的文字发生冲突,因为 std 定义的文字永远不会以 _ 开头,而用户定义的文字 必须 开始_

此外,对于库开发人员来说,是否有必要(或良好做法)在大型库的多个内联命名空间内没有冲突的重载?

这是一个非常好的问题,我认为陪审团仍然在这个问题上。但是,我恰好正在开发一个库,该库有目的地在不同的内联命名空间中有冲突的用户定义文字!

https://github.com/HowardHinnant/date

#include "date.h"
#include "julian.h"
#include <iostream>

int
main()

    using namespace date::literals;
    using namespace julian::literals;
    auto ymd = 2017_y/jan/10;
    auto jymd = julian::year_month_dayymd;
    std::cout << ymd << '\n';
    std::cout << jymd << '\n';

上面的代码编译失败,出现这个错误信息:

test.cpp:10:20: error: call to 'operator""_y' is ambiguous
    auto ymd = 2017_y/jan/10;
                   ^
../date/date.h:1637:1: note: candidate function
operator "" _y(unsigned long long y) NOEXCEPT
^
../date/julian.h:1344:1: note: candidate function
operator "" _y(unsigned long long y) NOEXCEPT
^

_y 文字用于在此库中创建 year。这个库有一个公历(在“date.h”中)和一个儒略历(在“julian.h”中)。这些日历中的每一个都有一个year 类:(date::yearjulian::year)。它们是不同的类型,因为公历年与儒略年不同。但是将它们都命名为 year 并同时赋予它们 _y 文字仍然很方便。

如果我从上面的代码中删除using namespace julian::literals;,那么它会编译并输出:

2017-01-10
2016-12-28

这表明 2016-12-28 Julian 与 2017-01-10 Gregorian 是同一天。这也是一个图形演示,同一天在不同的日历中可以有不同的年份。

只有时间会证明我使用冲突的_ys 是否会出现问题。迄今为止还没有。然而,没有多少人使用过这个库和非公历。

【讨论】:

所以,我确实从中得出,using namespace std; 不是一个好主意。使用更具体的命名空间如using namespace std::chrono_literals; 是?或者这是一个激进的结论?我知道这种情况下的重载并不冲突,但是将来呢?此外,对于库开发人员来说,在大型库的多个内联命名空间中是否有必要(或良好做法)没有冲突的重载?很难为这种语言特性得出个人指导方针...... @towi:更新答案。 感谢using namespace std 的全面更新,我应该更具体一些——当然我的意思是在函数或实现文件内部。但无论如何,谢谢(其他读者会喜欢的!)。而且您的 Hinnant-Date-Example 非常好,它向我表明,这些重载不会编译,而是标准的设计,这并不是编译器的意外。好的。最后对您的日期库示例进行评论:这是迄今为止我见过的最疯狂的日期类型安全实现...... :-) 我通读了前几段,就像“这可能是霍华德”:) 长篇大论,是的,我看得出来。 :-)

以上是关于std::literals::.. 作为内联命名空间有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

非内联命名空间不能作为内联重新打开

为啥内联未命名的命名空间?

封闭命名空间内联时的嵌套命名空间定义

内联命名空间中对命名空间的不明确引用

C++入门

当存在内联命名空间时,如何显式引用封闭的命名空间?