23

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

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

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

4

1 回答 1

38

用户定义的文字s不会在secondsand之间“冲突” 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 一样多。

test1()不会工作namespace literals没有inline

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

void test2()
{
    using namespace std::literals;
    auto str = "text"s;
    auto sec = 1s;
    string str2;  // error, string not declared.
}

这带来了所有标准定义的文字,但不是(例如)std::string

test2()如果namespace string_literalswas notinlinenamespace chrono_literalsis not将无法工作inline

您还可以选择公开字符串文字,而不是 chrono 文字:

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 名称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 namespaces 是使所有这些选项对开发人员可用的工具。

更新

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

using namespace std不是个好主意?

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

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

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

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

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

标准定义的文字永远不会与用户定义的文字冲突,因为标准定义的文字永远不会以 开头_,而用户定义的文字必须_.

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

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

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 Julian 与 2017-01-10 Gregorian 是同一天。这也是一个图形演示,同一天在不同的日历中可以有不同的年份。

只有时间会证明我使用冲突_y的 s 是否会有问题。迄今为止还没有。然而,没有多少人使用这个图书馆与非公历。

于 2016-08-21T14:47:44.397 回答