c++ - UTF-8 字符的预处理器粘贴在 g++ 中执行 'not give a valid preprocessing token'

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

鉴于以下应用程序,g++ 似乎无法连接 2 个 UTF-8 字符,但它可以很好地处理 ascii 范围的内容。这是预期的行为吗?我怎样才能使这项工作?我在 linux 环境中运行它,源文件保存为 UTF-8(尝试使用和不使用 BOM,均无济于事)。请注意,我需要在调用 C++ 编译器之前创建 token ,因此依赖 ("foo""bar") 变为 ("foobar") 在我的情况下不起作用,这让我很烦恼。

#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: error: pasting "▒" and "▒" does not give a valid preprocessing token sandbox.cpp:13:1: error: pasting "▒" and "▒" does not give a valid preprocessing token sandbox.cpp:13:1: error: pasting "▒" and "▒" does not give a valid preprocessing token sandbox.cpp:13:1: error: pasting "▒" and "▒" does not give a valid preprocessing token

最佳答案

token 连接——##预处理运算符(operator)——必须产生一个有效的 token ,它就是为了做到这一点。您可以使用它从不同的部分生成标识符。例如,您可以通过将一些前缀 ( label ) 与预处理器宏 __LINE__ 连接来生成唯一标签。 .

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

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

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

碰巧的是,OP 中的字符都属于这个受限子集。因此,在这个特定的示例中,切换到 Clang 预处理器会起作用。

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

  • C 会将两个(或更多)连续的字符串文字合并为一个字符串文字。例如,"着" "る""着る" 完全相同.因此,您可以使用类似以下的宏来分别对所需字符串的各个部分进行字符串化:
    #define QUOTE(x, y) Q(x) Q(y)
    

    在绝大多数情况下,这将是正确的解决方案。
  • 即使上述情况是不可能的——一个经典案例是 #include 中引用的文件名规范。语句——您仍然可以使用 stringify 运算符 ( # ),因为该运算符不仅适用于单个标记。 stringify 运算符的参数是一个宏参数,它是一个标记和空格的流。这样做时,有时使用“身份”宏#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(着,る);
    


  • 笔记
  • 一些不符合标准的预处理器(例如某些版本的 MSVC)可能允许使用两个 ## 将三个点粘贴在一起。运营商。但这不是可移植的。
  • 反斜杠转义如 \"也不是 token ;反斜杠转义序列仅在字符串文字中有意义。在程序文本中,\是一个单字符标记,除了作为续行序列的一部分出现的反斜杠,它们根本不是标记。
  • 但是,您可以使用标记连接来粘贴编码前缀,例如 L到一个无前缀的字符串文字上。
  • 关于c++ - UTF-8 字符的预处理器粘贴在 g++ 中执行 'not give a valid preprocessing token',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46538268/

    相关文章:

    c++ - 无法在 std::map 成员变量中分配具有前向声明值的类

    c++ - 循环 vector - 寻找最小可能的 'cost' (来自 CodeChef)

    encoding - notepad++ 显示 ucs-2LE 而 ubuntu FILE [file] 显示 UTF-16LE,我很困惑?

    c++ - 只返回非类型参数

    c++ - 对汇编函数的 undefined reference

    c - 宏中的 GCC 变量类型

    c++ - GCC 的 _Pragma 运算符中的预处理器标记粘贴

    python - UnicodeEncodeError Python

    php - 我想用 php 将西里尔字母发送到 mysqli 数据库

    c++ - 为什么类的析构函数被调用两次?