用户定义文字的每个“正常”使用都是未定义的行为吗?

Dam*_*mon 9 c++ language-lawyer

用户定义的文字必须以下划线开头。

这是一个或多或少众所周知的规则,您可以在每个谈论用户文字的外行网站上找到。这也是我(可能还有其他人?)一直公然无视的规则,因为“真是胡说八道”。当然,这绝对是不正确的。在最严格的意义上,这使用了一个保留标识符,因此会调用未定义的行为(尽管实际上你不会从编译器那里得到那么多的耸肩)。

因此,考虑我是否应该继续故意忽略标准的(在我看来无用的)部分,我决定查看实际编写的内容。因为,你知道,每个人都知道什么有什么关系。重要的是标准中写了什么。

[over.literal]指出“一些”字面后缀标识符是保留的,链接到[usrlit.suffix]. 后者声明所有都是保留的,除了以下划线开头的那些。好的,这几乎就是我们已经知道的,明确写的(或者更确切地说,是倒着写的)。

此外,[over.literal]包含一个提示,提示一个明显但令人不安的事情:

除了上面描述的约束,它们都是普通的命名空间范围函数和函数模板

好吧,他们肯定是。没有任何地方说它们不是,那么您还期望它们是什么。

但请稍等。[lex.name]明确声明全局命名空间中以下划线开头的每个标识符都是保留的。

现在,一个文字操作符通常,除非你明确地把它放入一个命名空间(我相信没有人这样做!?)在全局命名空间中非常多。因此,必须以下划线开头的名称是保留的。没有提到特殊的例外。因此,每个名称(带下划线或不带下划线)都是保留名称。

您是否确实希望将用户定义的文字放入命名空间,因为“正常”用法(下划线与否)使用的是保留名称?

Nic*_*las 6

是的:禁止使用_作为全局标识符的开头加上要求以非标准 UDL 开头的组合_意味着您不能将它们放在全局命名空间中。但是你不应该用东西弄脏全局命名空间,尤其是UDL,所以这应该不是什么大问题。

标准使用的传统习惯用法是将 UDL 放在literals命名空间中(如果您有不同的 UDL 集,则将它们放在inline namespaces该命名空间下方的不同位置)。该literals命名空间通常位于您的主命名空间之下。当您想要使用一组特定的 UDL 时,您可以调用using namespace my_namespace::literals或任何包含您选择的文字集的子命名空间。

这很重要,因为 UDL 往往被大量缩写。例如,标准使用sfor std::string,但也用于std::chrono::duration秒。虽然它们确实适用于不同类型的文字(s应用于字符串是字符串,而s应用于数字是持续时间),但有时阅读使用缩写文字的代码会令人困惑。所以你不应该向你图书馆的所有用户扔字面量;他们应该选择使用它们。

通过为这些 (std::literals::string_literalsstd::literals::chrono_literals)使用不同的命名空间,用户可以预先知道他们想要在哪些代码部分中使用哪些文字集。


Kon*_*lph 5

\n

每个 \xe2\x80\x9cnormal\xe2\x80\x9d 使用用户定义的文字是否未定义行为?

\n
\n\n

显然不是。

\n\n

下面是惯用语用法(因此肯定是 \xe2\x80\x9cnormal\xe2\x80\x9d),并且它\xe2\x80\x99s 根据您刚刚列出的规则\xe2\x80\x99 进行了明确定义:

\n\n
namespace si {\n    struct metre {\xc2\xa0\xe2\x80\xa6 };\n\n    constexpr metre operator ""_m(long double value) { return metre{value}; }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

您\xe2\x80\x99列出了有问题的案例,我同意您对其有效性的评估,但它们\xe2\x80\x99在惯用的C++代码中很容易避免,所以我不\xe2\x80\x99完全看到当前措辞的问题,即使这可能是偶然的。

\n\n

根据[over.literal]/8中的例子,我们甚至可以在下划线后面使用大写字母:

\n\n
\n
float operator ""E(const char*);    // error: reserved literal suffix (20.5.4.3.5, 5.13.8)\ndouble operator""_Bq(long double);  // OK: does not use the reserved identifier _Bq (5.10)\ndouble operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

因此,唯一有问题的事情似乎是该标准使""和 UDL 名称之间的空格变得重要。

\n

  • @Belloc *literal-operator-id* 有两种语法产生式。请注意,`""_Bq2` 是一个*用户定义的字符串文字*,一个预处理标记。而 `"" _Bq2` 是一个*字符串文字*,后跟一个*标识符*。两者都是有效的语法。但在后一种情况下,存在保留标识符,这违反了保留名称的规则。 (2认同)

Bri*_*ian 5

这是一个很好的问题,我不确定答案,但我认为基于对标准的特定阅读,答案是“不,这不是 UB”。

[lex.name]/3.2 内容如下:

每个以下划线开头的标识符都保留给实现以用作全局命名空间中的名称。

现在,显然,“作为全局命名空间中的名称”的限制应该被理解为适用于整个规则,而不仅仅是实现如何使用该名称。也就是说,它的意义不是

“每个以下划线开头的标识符都保留给实现,并且实现可以使用这样的标识符作为全局命名空间中的名称”

反而,

“使用任何以下划线开头的标识符作为全局命名空间中的名称都保留给实现”。

(如果我们相信第一种解释,那么就意味着没有人可以声明一个名为 的函数my_namespace::_foo。)

在第二种解释下,类似于operator""_foo(在全局范围内)的全局声明是合法的,因为这样的声明不用_foo作名称。更确切地,该标识符只是一个实际的名称,它是的一部分operator""_foo(它以下划线开头)。


Lig*_*ica 0

给定带有后缀的文字_X语法称为_X“标识符”

所以,是的:该标准可能无意中使得在定义良好的程序中创建全局范围的 UDT 或以大写字母开头的 UDT 变得不可能。(请注意,无论如何,前者都不是您通常想做的事情!)

这无法通过编辑解决:用户定义的文字的名称必须有自己的词法“名称空间”,以防止与(例如)实现提供的函数的名称发生冲突。不过,在我看来,如果在某处有一个非规范性注释,指出这些规则的后果并指出它们是故意的,那就太好了。