std::literals::.. 成为内联命名空间有什么好处?

What is the benefit of std::literals::.. being inline namespaces?

在 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 主要动机是 "library versioning",但这不适合这里。

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

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

user-defined 文字 s 不会 "clash" 在 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-defined 名称。在上面的测试中,我将整个 std 库 "imported" 放入 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-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
}

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

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

终于有办法公开所有计时名称 chrono_literals:

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 是一种工具,可让开发人员使用所有这些选项。

更新

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

Is using namespace std not a good idea?

视情况而定。 using namespace 在 header 的全局范围内从来都不是一个好主意,它意味着成为通用库的一部分。您不想将一堆标识符强加到用户的全局名称空间中。该命名空间属于您的用户。

如果 header 只存在于您正在编写的应用程序中,并且如果您认为您拥有所有这些标识符可用于包含 header 的所有内容。但是,您转储到全局范围内的标识符越多,它们与某些东西发生冲突的可能性就越大。 using namespace std; 引入了 bunch 标识符,并且会随着标准的每个新版本引入更多。所以我不建议 using namespace std; 在 header 的全局范围内,即使是你自己的应用程序也是如此。

但是我可以在 header 的全局范围内看到 using namespace std::literalsusing namespace std::chrono_literals,但仅针对应用程序 header,而不是库 header .

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

std-defined 字面值 可能 永远不会相互冲突(它们今天不会)。但你永远不知道...

std-defined 文字 永远不会 与 user-defined 文字冲​​突,因为 std-defined 文字永远不会以 _ 开头,并且 user-defined 文字 _.

开头

Also, for library developers, is it necessary (or good practice) to have no conflicting overloads inside several inline namespaces of a large library?

这是一个非常好的问题,我认为这个问题还没有定论。然而,我恰好正在开发一个库,该库 有目的地 在不同的内联命名空间中具有冲突的 user-defined 文字!

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 class:(date::yearjulian::year)。它们是不同的类型,因为公历年与儒略年不同。但是将它们都命名为 year 并给它们一个 _y 文字仍然很方便。

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

2017-01-10
2016-12-28

证明 2016-12-28 Julian 与 2017-01-10 Gregorian 是同一天。而且这也是一个图解展示,同一天在不同的历法中可以有不同的年份。

只有时间才能证明我对冲突 _y 的使用是否会出现问题。迄今为止还没有。然而,没有多少人将这个库与 non-Gregorian 日历一起使用。