std::literals::..作为内联命名空间的好处是什么?

26
在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解释主要动机是“库版本控制”,但这不符合这里的情况。

我可以看到“Seconds”和“String”的重载是不同的,因此它们不会冲突。但是如果重载相同,它们会冲突吗?或者(inline?) namespace可以防止这种情况发生?

因此,将它们放在一个inline namespace中有什么好处呢? 正如@Columbo在下面指出的那样,如何解决跨行内命名空间的重载,并且它们是否冲突?


1
我明白了,你的问题是关于跨越内联命名空间的重载。那就不是重复的问题了。 - Columbo
2
@Columbo确实如此。它也不是"What are inline namespaces for"的重复,因为那个答案是关于版本控制的语法。 - towi
关于 unsiged long long,也许这是 unsigned ... 的打字错误。 - agc
1个回答

40

用户定义的字面量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 定义名称。 在上面的测试中,我已经将整个 std 库“导入”到了 test 中,或者至少已经包含了尽可能多的内容。

如果没有将 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-defined字面量,但不包括(例如)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
}

或者只使用chrono字面值而不是字符串字面值:

void test4()
{
    using namespace std::chrono_literals;
    auto str = "text"s;   // error
    auto sec = 1s;
}

最终有一种方法可以公开所有chrono名称chrono字面值:

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;
}
总之,内联命名空间是为开发人员提供所有这些选项的工具。 更新 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_day{ymd};
    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朱利安历和2017-01-10公历是同一天。这也是表明在不同的日历中相同的日期可能有不同年份的图形演示。

只有时间可以告诉我们我对矛盾的“_y”使用是否会有问题。到目前为止还没有出现过问题。但是很少有人使用这个库与非公历日历一起使用。


因此,我得出结论,using namespace std; 不是一个好主意。使用更具体的名称空间,如 using namespace std::chrono_literals; 是好的吗?还是这个结论太极端了?我知道在这种情况下重载不会产生冲突,但将来呢?另外,对于库开发人员来说,在大型库的几个内联名称空间中是否需要(或者说是良好的做法)没有冲突的重载?很难从语言功能中推导出个人指南... - towi
1
@towi: 答案已更新。 - Howard Hinnant
感谢您对“using namespace std”的全面更新,我应该更加具体地说明 - 当然我是指在函数或实现文件内部。但还是谢谢(其他读者会喜欢的!)。而且,您的Hinnant-Date-Example非常出色,向我展示了那些重载不编译而是标准设计的原因,这不是编译器意外。很好。最后一句话是关于您的日期库示例:这是我到目前为止见过的最疯狂的日期类型安全实现... :-) - towi
4
我通读了前几段,心想“这可能是霍华德写的” :) - T.C.
1
冗长的吹牛大师,是的,我可以看出来。 :-) - Howard Hinnant
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接