有没有办法在枚举中使用关键字作为标识符?

Is there a way to use a keyword as identifier in an enum?

我一直无法找到是否可以在枚举定义中使用关键字,例如:

enum class EServerAction
{
    create,
    read,
    update,
    delete
};

在 C# 中,我可以使用 @ 字符让编译器将其视为标识符。有没有办法在 C++ 中执行此操作(Visual Studio 2015)?

不,它们不能使用。

来自MSDN

Keywords are predefined reserved identifiers that have special meanings. They cannot be used as identifiers in your program.

标识符的 rule 表示:

An identifier can be used to name objects, references, functions, enumerators, types, class members, namespaces, templates, template specializations, parameter packs, goto labels, and other entities, with the following exceptions:

  • the identifiers that are keywords cannot be used for other purposes;
  • the identifiers with a double underscore anywhere are reserved;
  • the identifiers that begin with an underscore followed by an uppercase letter are reserved;
  • the identifiers that begin with an underscore are reserved in the global namespace.

在 C++ 中,关键字不能像在 C# 中那样用作标识符。

可以使用宏来做到这一点:

#define delete _delete

enum class EServerAction
{
    create,
    read,
    update,
    delete
};

通常不鼓励这种做法,因为现在您不能在文件的其他部分使用 delete。但它在某些情况下很有用,例如使用 C++ 编译器编译具有名为 C++ 关键字(如 delete)的标识符的 C 程序时。

这也会在调试器中造成混淆,因为符号的标识符与源代码中的标识符不同。

根据 C++14 标准中的 2.12 [lex.key],某些标识符 绝不能 用作标识符:

The identifiers shown in Table 4 are reserved for use as keywords (that is, they are unconditionally treated as keywords in phase 7) except in an attribute token (7.6.1) [ Note: The export keyword is unused but is reserved for future use. — end note ]:

Table 4 — Keywords

   alignas continue friend register true
   alignof decltype goto reinterpret_cast try
   asm default if return typedef
   auto delete inline short typeid
   bool do int signed typename
   break double long sizeof union
   case dynamic_cast mutable static unsigned
   catch else namespace static_assert using
   char enum new static_cast virtual
   char16_t explicit noexcept struct void
   char32_t export nullptr switch volatile
   class extern operator template wchar_t
   const false private this while
   constexpr float protected thread_local
   const_cast for public throw

此外,一些标识符不得使用:

Furthermore, the alternative representations shown in Table 5 for certain operators and punctuators (2.6) are reserved and shall not be used otherwise:

Table 5 — Alternative representations

and and_eq bitand bitor compl not
not_eq or or_eq xor xor_eq

此外,根据 2.11 标识符 [lex.name],有些 非法使用 ,但编译器不需要告诉您:

some identifiers are reserved for use by C++ implementations and standard libraries (17.6.4.3.2) and shall not be used otherwise; no diagnostic is required

— Each name that contains a double underscore _ _ or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use.

— Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.

编辑
简答:否!

您只能使用 override 等上下文关键字作为标识符。但你不应该。该标准仅允许向后兼容,这可能会被删除或弃用。并且让它适用于每个关键字将意味着破坏该语言的许多其他语义。

考虑另一种命名枚举条目的方法。
结束编辑

注意
我试图通过使用 C-preprocessor 来实现它。 以下代码示例既不是良好做法,也不是标准所允许的。以下代码示例应仅演示 C-preprocessor 的行为。使用这些技术可能会导致问题,尤其是在使用库 headers 时,因为重新定义这些关键字会改变任何代码的语义。
尾注

对于某些名称,可以使用宏来执行此操作,这里所有关键字都可用作普通标识符,而不会丢失关键字本身的含义。首先,将它们存储到常量中或使用 nokw_truenokw_bool 等有效标识符对类型进行类型定义。然后用 keyword-name 定义宏来引用别名。

constexpr static nullptr_t nokw_nullptr = nullptr;
constexpr static bool nokw_true = true;
constexpr static bool nokw_false = false;
using nokw_bool = bool;
using nokw_void = void; // suprising: this can also be used for empty parameter list

#define nullptr nokw_nullptr
#define true nokw_true
#define false nokw_false
#define bool nokw_bool
#define void nokw_void

注意
我测试了这段代码,它工作 就好像 宏从未定义过一样。但不能保证它在每个 C++ 编译器上的行为都相同,因为它 不一样 ,关键字与标识符有很大不同。而且它与较新的 C++ 标准不向前兼容,因为它们可以使用这些关键字添加新语法,这对于引擎盖下的标识符是无效的。
尾注

对于 intchar 等其他基本类型,当使用 [=22= 重新声明时,将无法再使用它们,例如 unsigned long intsigned char ]/typedef。类型 whar_tchar16_tchar32_t 大多数时候由 typedef 声明,因此不是 "real" 关键字,但如果您的编译器直接将其实现为关键字,您可以使用 using nokw_* = *; 方法将其用作标识符。
注意 千万不要这样做! 尾注

*_cast-关键字也可以作为 non-keyword,通过在函数中定义它们:

    template<typename N, typename O>
N nokw_reinterpret_cast(O&& old) { return reinterpret_cast<N>(old); };

#define reinterpret_cast nokw_reinterpret_cast
// other cast-operators follow

无法定义其他关键字,因为它们既不是类型,也不是像 cast-operators 那样的 function-like 关键字。在您的情况下,delete-关键字不能与标识符同时存在,因为 delete 具有特殊的句法含义。考虑 delete ptr 或删除函数。

但是使用上面的定义可以实现带有关键字 truefalsetribool 作为枚举。 class-关键字在这里很重要,因为名称可能与先前定义的名称冲突names/the 标识符的使用不明确。

enum class tribool { true, maybe, false };

或者它也可以在命名空间中:

// maybe writing an API for a Visual Basic Application
namespace vb_constants {
    constexpr int true = -1; // in VB True is -1
    constexpr int false = 0;
    // in this namespace the real true and the real false can
    // be accessed through ::true and ::false
};

enum-entries可以像tribool::true一样使用。

然而:许多程序员使用 CAPITALIZED enum-entry-names。因为没有大写关键字,所以这些将始终有效...
注意
这既不违反 C++ 标准也不违反 C-standard。这也是为什么经常建议使用大写宏名的原因。宏对定义后包含的所有文件全局可见,其他实体可以并且应该嵌套在命名空间中。否则可能会导致名称冲突。当您需要将某些东西定义为宏时,您应该在宏名称前添加一些独特的文本,例如您的 library/app 名称,就像您命名 C++ 名称空间一样。这也被称为命名空间,在 C++ 甚至有命名空间之前。以 OpenGL 为例,所有标识符都以 GL 为前缀,这与 C++ 名称空间一样有效,但它不允许构造为 using ns::the_classusing namespace,但是你永远不应该使用它们(全球)。
尾注

我会选择

enum class EServerAction {
    CREATE,
    READ,
    UPDATE,
    DELETE
};

注意
当我写这篇文章时,我试图保留关键字的所有语义;但即使通过将关键字重新定义为宏来触及关键字也是非常糟糕的,因为标准禁止这样做。您可能永远不知道 libraries/standard 库 implementations/specific 系统 headers 如何使用或可能重新定义它们。
尾注

使用 MSVC __identifier: https://docs.microsoft.com/en-us/cpp/extensions/identifier-cpp-cli?view=vs-2019

enum class EServerAction
{
    create,
    read,
    update,
    __identifier(delete)
};