C++11中原始字符串字面量R"(...)"中为什么要使用括号?

84

C++11引入了一种非常方便的特性,称为原始字符串字面量,它们是没有转义字符的字符串。而不是写成这样:

  regex mask("\\t[0-9]+\\.[0-9]+\\t\\\\SUB");

您只需简单地写下这段代码:
  regex mask(R"(\t[0-9]+\.[0-9]+\t\\SUB)");

非常易读。然而,请注意在定义原始字符串字面值时需要放置额外的括号。

我的问题是,为什么我们需要这些呢?对我来说,它看起来很丑陋和不合逻辑。以下是我所看到的缺点:

  • 额外冗长,而整个功能的用途是使文字更紧凑
  • 很难区分文字的主体和定义符号

这就是我所说的难以区分 :

"good old usual string literal"
 ^-    body inside quotes   -^

R"(new strange raw string literal)"
   ^- body inside parenthesis  -^

以下是优点:

  • 更灵活,原始字符串中有更多可用字符,特别是在使用分隔符时:"delim( can use "()" here )delim"

但是,如果您需要更多的灵活性,可以使用旧的转义字符串。为什么标准委员会决定用这些绝对不必要的括号污染每个原始字符串文字的内容?背后的理论是什么?我没有提到的优点是什么?

UPD Kerrek的答案很好,但很遗憾,这不是一个答案。因为我已经描述了我理解它如何工作以及它带来的好处。自从我问这个问题以来已经过去了五年,仍然没有答案。我对这个决定感到沮丧。有人可能会说这是品味问题,但我不同意。你使用多少空格,如何命名你的变量,这是SomeFunction()还是some_function() - 这是品味问题。我可以很容易地从一种风格切换到另一种风格。

但是这个?..这么多年过去了,仍然感觉笨拙和笨重。不,这不是关于品味的问题。这是关于我们想要涵盖所有可能情况的方式,无论如何。我们注定要每次需要编写特定于Windows的路径、正则表达式或多行字符串文字时都写入这些丑陋的括号。为什么?..为了那些我们实际上需要在字符串中放置"的罕见情况吗?我希望我能参加委员会会议,他们决定以这种方式做。我会强烈反对这个非常糟糕的决定。我希望。现在我们注定了。

感谢您阅读到这里。现在我感觉好多了。

UPD2 这是我的替代提案,我认为这两个提案都比现有的好得多。

提案1. 受Python启发。不支持三重引号的字符串字面量:R"""这是一个字符串文字,其中包含任何内容,除了你不经常使用的三重引号。"""

提案2. 受常识启发。支持所有可能的字符串字面量,就像当前的一样:R"delim"content of string"delim"。空分隔符:R""看起来更好,不是吗?""。空原始字符串:R""""。带双引号的原始字符串:R"#"这里有双引号:"", 谢谢"#"

这些提案有什么问题吗?


15
R";-](R"(this is a basic raw string literal as text inside a more complex one)");-]" 的翻译是:R";-](R"(这是一个基本的原始字符串字面量,作为更复杂字符串中的文本部分)");-]" - oblitum
语法确实很丑,但我真的想不到其他的替代方案,既要保持向后兼容性,又要保留所有功能。 - ChilliDoughnuts
1
@Mikhail:“对于那些我们实际上需要在字符串中使用“的罕见情况?”您认为在原始字符串中需要"的情况很少,这可能是问题的一部分。问题不在于没有答案。有一个答案,只是您不同意它。如果您对“答案”的定义是“让我改变对此的看法的东西”,那么您的问题就过于主观了。已经提供了解释;不需要得到您的同意。 - Nicol Bolas
1
你不应该更新一个历史上得到高赞的问题来包含一个新问题……而是要发布一个新问题。(然而,由于你唯一的反对理由似乎是“我觉得这不美观”,所以新问题可能会被关闭为基于个人观点的。) - M.M
@M.M 这个问题本来就没有被接受的答案。我根据评论中的逻辑要求进行了更新。 - Mikhail
显示剩余2条评论
3个回答

111

括号的目的是让您可以指定自定义分隔符:

R"foo(Hello World)foo"   // the string "Hello World"
在您的示例中,以及在典型的用法中,定界符只是空的,因此原始字符串由序列R"()"括起来。
允许任意定界符是一种设计决策,反映了提供完整解决方案而不带奇怪限制或边缘情况的愿望。你可以选择任何不出现在字符串中的字符序列作为定界符。
如果没有这个功能,如果字符串本身包含像"(如果你只想要R"..."作为原始字符串语法)或)"(如果定界符为空)之类的内容,那么你将会遇到麻烦。这两个都是非常常见和频繁的字符序列,特别是在正则表达式中,因此如果你使用原始字符串取决于你的字符串的具体内容,那将是非常恼人的。
请记住,在原始字符串内部没有其他转义机制,所以否则您唯一能做的最好方法是连接字符串字面量的片段,这将非常不实用。通过允许自定义定界符,你只需要选择一个不寻常的字符序列一次,并且在未来进行修改时可能偶尔需要修改它。
但要再次强调,即使空定界符已经很有用了,因为R"(...) "语法允许您在字符串中放置裸引号。这本身就是一个相当大的收获。

10
裸露的换行符、制表符和空格! - Петър Петров
4
当然,我想强调的是,()不是用来允许反斜杠和空格的。只有在字符串中含有")"时才需要使用分隔符。例如,R"("(eg)")"必须使用分隔符,如R"delim("(eg)"))delim"。我有些同意这种语法有点难以掌握,在这个例子中,"\ "(eg)""对我来说更易读。 - Superfly Jon
1
@AndyG:我的意思是,包括括号在内,)foo 在你的字符串中并没有出现。d-char-sequence 本身确实可以随意出现。 - Kerrek SB
3
@Mikhail:并不是每个字符串都需要使用原始字符串字面量。这是一个判断的问题,当它能够改善情况时才使用。典型的用例是有一个长或复杂的字符串,这样你在阅读时可以集中精力看其内容,基本上忽略定界符。 - Kerrek SB
9
更准确地说,)foo 可以出现在字符串内部,但 )foo" 不能。R"foo(Hello World )foo)foo" 等同于 "Hello World )foo" - isarandi
显示剩余7条评论

11
如其他答案所述,在字符串中出现")"等关闭序列时,必须有其他内容与引号配合使用,以避免解析歧义。
至于语法选择,我同意语法选择不是最佳的,但通常情况下可以接受。可以这样想: "情况可能更糟",哈哈。我认为这是使用简便性和解析简易性之间的一个很好的折衷方案。 提议1: 受Python启发的提议。不能支持带有三重引号的字符串字面量:
R"""任何内容,除了你并不经常使用的三重引号。"""
实际上,这确实存在问题--"几乎不使用的引号"。首先,原始字符串的概念就是要表示原始字符串,即正好与文本文件中的字符串相同,而不管字符串内容如何,都不进行任何修改。其次,语法应该是通用的,也就是说,不添加任何变化,比如“几乎是原始字符串”等。
你会如何使用这种语法来编写一个引号?两个引号?请注意——这些都是非常常见的情况,特别是当您的代码涉及字符串和解析时。 提议2:
R"定界符"字符串内容"定界符"。 R""看起来更好,不是吗?""。 R"#"这里是双引号: "", 谢谢"#"。
嗯,这个可能是更好的选择。但有一件事——一个常见情况(我认为这是接受的语法的一个动机)是双引号字符本身非常常见,原始字符串应该对这些情况很有用。
那么,我们看看正常字符串语法:
s1 = "\"";
s2 = "\"quoted string\"";

你的语法,例如使用“x”作为分隔符:
s1 = R"x"""x";
s2 = R"x""quoted string""x";

接受的语法:

s1 = R"(")";
s2 = R"("quoted string")";

是的,我同意括号引入了一些令人讨厌的视觉效果。因此,我怀疑语法的作者们想法是这种情况下附加的“delim”很少需要,因为)"不经常出现在字符串中。但是另一方面,尾随/前导/孤立的引号相当常见,因此您提出的语法(#2)会更频繁地需要一些delim,这反过来又需要更频繁地将其从R""..""更改为R"delim"..."delim"。希望你明白了。

语法是否可以更好?我个人更喜欢更简单的语法变体:

Rdelim"string contents"delim;

通过以上示例:

s1 = Rx"""x; 
s2 = Rx""quoted string""x;

然而,要使其正确工作(如果在当前语法中可能实现),此变体需要限制delim部分的字符集,例如仅限于字母/数字(因为存在操作符),并且可能还需要对初始字符进行进一步约束,以避免与可能的未来语法发生冲突。
因此,我认为可以做出更好的选择,尽管在这种情况下并没有什么显著更好的方法。


感谢您详细的回答!这实际上更接近我想要看到的内容。“另一方面,尾随/前导/孤立引号经常出现”-好吧,我没有这样的感觉。但这只是我的感觉。也许如果您分析大量公共代码库,您会发现实际情况是这样。但对我来说,感觉不同。 - Mikhail
好的例子,使用“引用字符串”。但是,嘿,你是不是想说原始字符串字面值在所有情况下都应该尽可能好看?我只想为非原始字符串字面值不够好的情况进行优化。对于你提供的两个例子,我实际上更喜欢非原始字符串字面值。这就是为什么我不太在意原始字符串字面值的外观。但我理解你的观点。谢谢。 - Mikhail
@Mikhail "对于非原始字符串字面量不够好的情况"。任何需要进行某种转义的字面量都不适用于许多任务(例如,放置带有DSL内容的字符串,例如JSON、Regex等)。因此,我认为这种字面量必须是真正的原始字符串,而不是半成品,因此现有的语法符合我对正确技术解决方案的期望。 - Mikhail V
是的,必须注意分隔符,但这至少比转义序列更容易看到。如果在解析阶段字符串终止位置错误 - 大多数情况下会看到一些错误,但在不正确转义序列的情况下,有更多难以发现的错误和更多的打字烦恼。 - Mikhail V

3
问题询问了语言决策的理由,因此回顾一下在标准化之前由委员会成员发布的文件是很有用的。以下信息是通过查看cppreference.com上支持C++11的编译器页面上的提案列表,并追溯N2442的历史得到的。

N2053 (2006-09-06)

最早成为C++11原始字符串字面量的提案是Beman Dawes在2006年提出的N2053。该提案提供了两个激励性的例子,一个是庞大的正则表达式,另一个是一个简短的HTML片段。这两个例子都包含了字面上的"字符,因此设计者明显认为支持字符串字面量中的双引号是重要的(而问题描述它们为“罕见”)。

N2053提议的原始字符串通常看起来像:

R""Hello, world!""

请注意,这与问题中的“提案2”类似,证明委员会考虑过但最终拒绝了它。
N2053允许内部的"可以是任何满足std::ispunct为真的字符,因此它也允许例如:
R"$Hello, world!  Embedded double-quotes like this "" are ok here.$"

N2146(2007-01-09)

下一次迭代是由Beman Dawes完成的N2146。在N2146中,原始字符串通常如下所示:

R"[Hello, world!]"

它还允许在引号和括号之间使用自定义的分隔符字符串:
R"DELIM[Hello, world!]DELIM"

改用方括号而不是引号的理由是“常见用例将使用易于识别的 R"[...]"”。
问题认为从字符串中“很难区分”定界符。至少在使用方括号而不是圆括号的语法时,Beman Dawes 显然持相反观点。
允许自定义定界符的理由是显而易见的,即“减少原始字面字符串包含与定界符相同序列的风险”。
N2295(2007-06-23)、N2384(2007-08-03)和 N2442(2007-10-05)是接下来的三个版本。
每个版本都由 Lawrence Crowl 和 Beman Dawes 完成,链接如下: N2295, N2384N2442
这些迭代对分隔符语法没有进行任何更改;在所有三个版本中,它保持不变:
R"DELIM[Hello, world!]DELIM"

N2295在陈述中虽然删除了激励性的例子和设计原理(尽管很简短),但同时也声明:“原始提案中的动机、讨论和其他细节保持不变。”哼。
C++11标准化过程中似乎没有更多公开讨论该功能的内容,直到它出现在C++11标准的第2.14.5节中,采用了现在熟悉的语法,包括双引号、圆括号和可选的自定义分隔符字符串。
R"DELIM(Hello, world!)DELIM"

我猜想方括号被改成圆括号,是因为后者是ISO 646(对应ASCII的国际标准)中不变的代码点。因此,使用方括号的话,一些使用非美国字符编码的用户将不得不使用trigraphs来使用原始字符串。

与Python三引号的比较

问题提出了两种替代方案,第一种是类似Python的三引号:
R"""Hello, world!"""

首先,我要指出N2053明显受到了Python的明确启发,所以它的作者显然考虑过这一点,但选择了不同的路线。
在N2053中,原始字符串字面量总是由一个两字符序列界定,显然认为两个双引号在大多数情况下足够使用。根据该功能的后续演变,我推测委员会成员最终偏爱R"(...)"而不是R"""...""",因为在典型用法中更加简洁。
与双引号对之间的自定义分隔符进行比较
问题的第二个提议的替代方案是在引号中使用分隔符。
R"DELIM"Hello, world!"DELIM"

这很接近于 N2146 所描述的:

R"DELIM[Hello, world!]DELIM"

正如已经指出的,作者明显认为在这个角色中使用方括号比双引号更容易被视觉识别。我推测他们对圆括号也持有同样的看法。

总结

简而言之,提供的备选方案或类似变体在考虑过后最终被拒绝了。这些历史记录明确指出了一些原因,但也存在一些无法填补的空缺,只能根据公开可得的信息进行推测。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接