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

时间:2016-08-21 14:21:06

标签: c++ namespaces c++14 literals inline-namespaces

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

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

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的主要动机是"库版本控制",但这不适合这里。

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

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

1 个答案:

答案 0 :(得分:35)

用户定义的文字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,或至少与#include一样多。

如果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定义的文字,但不会(例如)std::string

如果test2()不是namespace string_literalsinline不是namespace chrono_literals

inline将无效。

你也可以选择只是公开字符串文字,而不是时间文字:

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在下面提出了一些很好的后续问题。它们(希望)在本次更新中得到解决。

  

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。这个图书馆既有公历又有#34; date.h&#34;和Julian日历(&#34; julian.h&#34;)。这些日历中的每一个都有一个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是否有问题。到目前为止还没有。然而,没有多少人将这个图书馆与非公历日历一起使用。