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
不会在seconds
和string
之间“冲突”,即使它们都在范围内,因为它们像任何其他函数对一样在它们不同的参数列表上重载:
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
不是inline
,test1()
将无法工作。
这是使用文字的更受限制的方式,无需导入整个 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
不是inline
,test2()
将不起作用。
您也可以选择只公开字符串文字,而不是计时文字:
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 namespace
s 是一种工具,可让开发人员使用所有这些选项。
更新
OP 在下面提出了一些很好的后续问题。他们(希望)在本次更新中得到解决。
using namespace std
不是个好主意吗?
这取决于。 using namespace
在作为通用库一部分的标头中的全局范围内绝不是一个好主意。您不想将一堆标识符强制放入用户的全局命名空间中。该命名空间属于您的用户。
一个全局范围using namespace
可以在一个标头中使用,前提是该标头仅存在于您正在编写的应用程序中,并且如果您认为所有这些标识符都可用于包含该标头的所有内容,则您可以接受。但是,您转储到全局范围内的标识符越多,它们就越有可能与某些东西发生冲突。 using namespace std;
引入了 一堆 标识符,并且随着标准的每个新版本都将引入更多标识符。所以我不推荐 using namespace std;
在标头中的全局范围内,即使对于您自己的应用程序也是如此。
但是,我可以在标头的全局范围内看到 using namespace std::literals
或 using 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::year
和julian::year
)。它们是不同的类型,因为公历年与儒略年不同。但是将它们都命名为 year
并同时赋予它们 _y
文字仍然很方便。
如果我从上面的代码中删除using namespace julian::literals;
,那么它会编译并输出:
2017-01-10
2016-12-28
这表明 2016-12-28 Julian 与 2017-01-10 Gregorian 是同一天。这也是一个图形演示,同一天在不同的日历中可以有不同的年份。
只有时间会证明我使用冲突的_y
s 是否会出现问题。迄今为止还没有。然而,没有多少人使用过这个库和非公历。
【讨论】:
所以,我确实从中得出,using namespace std;
不是一个好主意。使用更具体的命名空间如using namespace std::chrono_literals;
是?或者这是一个激进的结论?我知道这种情况下的重载并不冲突,但是将来呢?此外,对于库开发人员来说,在大型库的多个内联命名空间中是否有必要(或良好做法)没有冲突的重载?很难为这种语言特性得出个人指导方针......
@towi:更新答案。
感谢using namespace std
的全面更新,我应该更具体一些——当然我的意思是在函数或实现文件内部。但无论如何,谢谢(其他读者会喜欢的!)。而且您的 Hinnant-Date-Example 非常好,它向我表明,这些重载不会编译,而是标准的设计,这并不是编译器的意外。好的。最后对您的日期库示例进行评论:这是迄今为止我见过的最疯狂的日期类型安全实现...... :-)
我通读了前几段,就像“这可能是霍华德”:)
长篇大论,是的,我看得出来。 :-)以上是关于std::literals::.. 作为内联命名空间有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章