对UTF-8字符进行预处理器粘贴并不能提供有效的预处理令牌'用g ++

时间:2017-10-03 06:20:25

标签: c++ gcc utf-8 g++

鉴于以下应用,g ++似乎无法连接2个UTF-8字符,但它可以很好地处理ascii范围内容。这是预期的行为吗?我怎样才能做到这一点?我在linux环境中运行它,并且源文件保存为UTF-8(尝试使用和不使用BOM,但无济于事)。注意我需要这个来在调用C ++编译器之前创建令牌,所以依赖于(" foo"" bar")成为(" foobar")赢了&#39在我的情况下工作,很多我的烦恼。

#include <iostream>
#include <string>

using namespace std;

#define Q(x) #x
#define QUOTE(x, y) Q(x ## y)

#define RU2(root) cout << QUOTE(root,る) << #root << QUOTE(root,れ) << QUOTE(root,ろ) << QUOTE(root,よ) << endl;

int main()
{
    RU2(着);
    return 0;
}
  

sandbox.cpp:13:1:错误:粘贴&#34;▒&#34;和&#34;▒&#34;没有给出有效的   预处理令牌sandbox.cpp:13:1:错误:粘贴&#34;▒&#34;和&#34;▒&#34;不   不提供有效的预处理令牌sandbox.cpp:13:1:错误:粘贴   &#34;▒&#34;和&#34;▒&#34;不提供有效的预处理令牌   sandbox.cpp:13:1:错误:粘贴&#34;▒&#34;和&#34;▒&#34;没有给出有效的   预处理令牌

2 个答案:

答案 0 :(得分:2)

令牌连接 - ##预处理操作符 - 必须生成有效令牌,并且它的目的就是这样做。您可以使用它从不同的部分生成标识符。例如,您可以通过将某些前缀(label)与预处理器宏__LINE__连接来生成唯一标签。

但是您不能使用令牌连接来生成非令牌,并且您不能总是使用它来生成有效令牌。例如,您无法使用##..粘贴在一起,因为..不是有效令牌,这意味着无法生成令牌...因为它无法分解为两个有效令牌。 [注1]。类似地,不可能通过标记粘贴两个部分来创建字符串文字(这是一个标记),因为没有标记可以包含单个(或')。[注2] [注3]。

标识符令牌由“identifier-nondigit”后跟任意数量的“digit”或“identifier-nondigit”字符组成。 “identifier-nondigit”可以是 _ ,一个Ascii字母,一个“通用字符名称”(即\u后跟四个十六进制数字或\U后跟八个十六进制数字)或“其他实现定义的字符”。通用字符名称必须始终为有效的Unicode代码点,并且标识符中使用的名称必须属于Unicode的受限子集,在C标准的附录D(规范性)中指定。

对于GCC,唯一的“其他实现定义的字符”是 $ ,并且只有在指定了适当的命令行标志时才是。因此,使用非Ascii Unicode字符的唯一方法是使用通用字符名称。另一方面,Clang没有这个限制;附录D中定义的子集中的Unicode字符可以直接用于标识符。

碰巧,OP中的角色都属于这个受限制的子集。因此,在这个特定的例子中,切换到Clang预处理器是可行的。

但是,实际上没有必要依赖非可移植扩展来创建具有扩展字符的字符串文字。通常,字符串文字的内容不需要是有效的标记,并且您不能通过字符串化标记连接的结果来制造任意字符串文字。但这无关紧要,原因有两个:

  1. C会将两个(或更多)连续的字符串文字合并为一个字符串文字。因此,例如,"着" "る""着る"完全相同。因此,您可以使用类似以下内容的宏来单独字符串化所需字符串的各个部分:

    #define QUOTE(x, y) Q(x) Q(y)
    

    在绝大多数情况下,这将是正确的解决方案。

  2. 即使上述情况不可能 - 经典案例是#include语句中引用的文件名规范 - 您仍然可以使用stringify运算符(#),因为该运算符不仅适用于单个令牌。 stringify运算符的参数是一个宏参数,它是一个标记和空格流。执行此操作时,有时使用“identity”宏#define I(X) X是有用的,以避免引入不需要的空格。例如:

    // Extra level of indirection in order to forced substitution
    // of the argument to Q
    #define Q_(X) #X
    #define Q(X) Q_(X)
    // Identity macro
    #define I(X) X
    
    // Stringify two arguments without intervening whitespace
    #define QUOTE2(X,Y) Q(I(X)I(Y))
    const char* s = QUOTE2(着,る);
    
  3. 注释

    1. 某些符合标准的非标准预处理器(例如某些版本的MSVC)可能允许使用两个##运算符将三个点粘贴在一起。但这不便携。

    2. \"这样的反斜杠转义也不是令牌;反斜杠转义序列仅在字符串文字中有意义。在程序文本中,\是单字符标记,除了作为行连续序列的一部分出现的反斜杠,它们根本不是标记。

    3. 但是,您可以使用标记连接将编码前缀(例如L)粘贴到未加前缀的字符串文字中。

答案 1 :(得分:1)

我认为GCC目前并不直接支持这一点。手册说明input character sets

  

在标识符中,ASCII范围之外的字符只能使用“\u”和“\U”转义符指定,不能直接使用。

遵循这个建议,我将你的程序改为:

#include <iostream>
#include <string>

using namespace std;

#define Q(x) #x
#define QUOTE(x, y) Q(x ## y)

#define RU2(root) \
  cout << QUOTE(root,\u308b) << #root << QUOTE(root,\u308c) \
  << QUOTE(root,\u308d) << QUOTE(root,\u3088) << endl;

int main()
{ 
  RU2(\u7740);
  return 0;
}

它似乎按预期工作。