用户定义的文字必须以下划线开头。
这是一个或多或少众所周知的规则,您可以在每个谈论用户文字的外行词站点上找到。自从“胡说八道”以来,我(甚至可能是其他人)一直公然忽略了这条规则。当然,现在这绝对是不正确的。从最严格的意义上讲,它使用保留的标识符,从而调用未定义的行为(尽管实际上,编译器不会给您耸耸肩的麻烦。)
因此,考虑是否应该继续故意忽略标准的那部分(我认为无用),我决定查看实际编写的内容。因为,每个人都知道,这有什么关系。重要的是标准中写的是什么。
[over.literal]
声明保留了“一些”文字后缀标识符,它们链接到[usrlit.suffix]
。后者指出全部保留,但下划线开头的保留。好的,这几乎就是我们已经知道的,明确编写的(或者相反编写的)。
此外,[over.literal]
包含一个 Note ,它暗示了明显但令人困扰的事情:
除上述约束外,它们是普通的命名空间范围函数和函数模板
好吧,确定是。它无处表明它们不是,所以您还希望它们成为什么。
但是请稍等。 [lex.name]
明确指出,每个在全局命名空间中以下划线开头的标识符都是保留的。
现在,通常是文字运算符,除非您将其显式地放入全局名称空间中的名称空间(我相信没有人这样做!?)。因此,该名称(必须以下划线开头)必须保留。没有提及特殊例外。因此,每个名称(带下划线或不带下划线的名称)都是保留名称。
您是否真的希望将用户定义的文字放到命名空间中,因为“普通”用法(是否带有下划线)使用的是保留名称?
答案 0 :(得分:6)
是:禁止将_
用作全局标识符的开头,再加上要求非标准UDL以_
开头的组合意味着您不能将它们放在全局标识符中命名空间。但是您不应该使用尤其是 UDL来弄脏全局命名空间,所以这不是什么大问题。
标准使用的传统习惯用法是将UDL放在literals
命名空间中(如果您有不同的UDL集,则将它们放在该命名空间下的不同inline namespaces
中) 。 literals
名称空间通常位于您的主要名称空间之下。当您要使用一组特定的UDL时,可以调用using namespace my_namespace::literals
或包含您选择的字面量的任何子命名空间。
这很重要,因为UDL往往大量缩写。例如,标准使用s
表示std::string
,但也使用std::chrono::duration
秒。尽管它们确实适用于不同种类的文字(适用于字符串的s
是字符串,而适用于数字的s
则是持续时间),但有时阅读使用缩写文字的代码可能会造成混淆。因此,您不应该对库的所有用户都使用文字。他们应该选择使用它们。
通过为这些名称空间使用不同的命名空间(std::literals::string_literals
和std::literals::chrono_literals
),用户可以预先了解他们希望在代码的哪些部分中使用哪些文字集。
答案 1 :(得分:3)
这是一个很好的问题,我不确定答案,但根据标准的特定阅读,我认为答案是“不,不是UB”。
[lex.name] /3.2读取:
以下划线开头的每个标识符都保留给实现,以用作全局命名空间中的名称。
现在,很显然,“作为全局命名空间中的名称”的限制应理解为适用于整个规则,而不仅仅是实现可能如何使用该名称。也就是说,它的含义不是
“每个以下划线开头的标识符都保留给实现,并且实现可以使用诸如全局名称空间中的名称之类的标识符”
但是,
“使用以下划线开头的任何标识符作为全局命名空间中的名称,都保留给实现”。
(如果我们相信第一种解释,那么这意味着没有人可以声明一个名为my_namespace::_foo
的函数。)
在第二种解释下,类似于operator""_foo
的全局声明(在全局范围内)是合法的,因为这样的声明不使用_foo
作为名称。相反,标识符只是实际名称的一部分,operator""_foo
(不是以下划线开头)。
答案 2 :(得分:2)
用户定义文字的每次“正常”使用都是未定义的行为吗?
显然不是。
以下是UDL的惯用(因此也绝对是“正常”)使用,并且根据您刚刚列出的规则进行了明确定义:
namespace si {
struct metre { … };
constexpr metre operator ""_m(long double value) { return metre{value}; }
}
您已经列出了有问题的案例,我同意您对它们的有效性的评估,但是在惯用的C ++代码中很容易避免它们,因此即使当前措辞可能是偶然的,我也无法完全看到其问题。< / p>
根据[over.literal] / 8中的示例,我们甚至可以在下划线后使用大写字母:
float operator ""E(const char*); // error: reserved literal suffix (20.5.4.3.5, 5.13.8) double operator""_Bq(long double); // OK: does not use the reserved identifier _Bq (5.10) double operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)
因此,唯一有问题的事情似乎是该标准使得""
与UDL名称之间的空白很重要。
答案 3 :(得分:1)
是的,在全局名称空间中定义自己的用户定义文字会导致程序格式错误。
我自己没有遇到这个问题,因为我尝试遵循以下规则:
请勿在全局名称空间中放置任何内容(除了main
,名称空间和extern "C"
东西以确保ABI稳定性)。
namespace Mine {
struct meter { double value; };
inline namespace literals {
meter operator ""_m( double v ) { return {v}; }
}
}
int main() {
using namespace Mine::literals;
std::cout << 15_m.value << "\n";
}
这也意味着即使在命名空间中也不能使用_CAPS
作为文字名称。
称为literals
的内联名称空间是打包用户定义的文字运算符的好方法。可以将它们导入到要使用它的位置,而不必确切命名所需的文字,或者如果导入整个命名空间,您也可以获取这些文字。
这遵循std
库如何处理文字的情况,因此代码用户应该熟悉。
答案 4 :(得分:0)
给出带有后缀_X
,the grammar calls _X
an "identifier"的文字。
因此,是的:大概是由于疏忽,该标准使得无法在定义良好的程序中在全局范围内创建UDT或以大写字母开头的UDT。 (请注意,前者通常不是您想做的事情!)
这不能用编辑方式解决:用户定义文字的名称必须具有自己的词法“名称空间”,以防止与(例如)实现提供的功能名称发生冲突。不过,我认为,在某处出现非规范性注释,指出这些规则的后果,并指出它们是故意的,将是很好的。