Alternative Operators in C++: and, or, and not

C++ includes English-word representations for operators—such as and, or, and not—as core keywords within the language rather than macro definitions. This article explores how these alternative operator representations originated from international character encoding limitations in early computing hardware, how they were introduced through the C standard via <iso646.h>, and how they were permanently elevated into first-class keywords during the standardization of C++98.

The Problem: Character Encoding and ISO 646

During the early decades of computing, keyboards, terminals, and display devices across the world did not share a single unified character set like Unicode. Instead, systems relied on 7-bit character standards, most notably ISO/IEC 646. While the American variant of ISO 646 (ASCII) included punctuation characters such as |, ~, ^, [, ], {, and }, international variants often replaced these specific punctuation marks with localized letters, currency symbols, or diacritics (such as German umlauts or Scandinavian letters).

This created major friction for programmers working on non-US keyboards and systems. Standard C and C++ syntax relied heavily on symbols that were either unavailable or rendered incorrectly:

  • && and || for logical operations
  • &, |, ^, and ~ for bitwise operations
  • ! for logical negation

Programmers using certain national terminal configurations could not easily type or display these characters, making C source code difficult to write, read, and port across regions.

The C Solution: ISO C Amendment 1 and <iso646.h>

To address these internationalization hurdles, the ANSI/ISO C standards committee released ISO/IEC 9899:1990/Amd.1:1995 (commonly called Normative Addendum 1). This amendment introduced two mechanisms: digraphs (two-character sequences like <: for [ and %> for }) and the <iso646.h> header.

In C, the alternative tokens were introduced as simple preprocessor macros defined inside <iso646.h>:

#define and     &&
#define or      ||
#define not     !
#define not_eq  !=
#define bitand  &
#define bitor   |
#define xor     ^
#define compl   ~
#define and_eq  &=
#define or_eq   |=
#define xor_eq  ^=

While this enabled cross-platform development on restricted hardware, it required explicit inclusion of the header and introduced the well-known drawbacks of C preprocessor macros, such as macro collision with variable or function names.

Elevation to Native Keywords in C++98

When Bjarne Stroustrup and the ISO C++ standardization committee developed the first official C++ standard (ISO/IEC 14882:1998, or C++98), they took a cleaner, more robust approach. Instead of implementing these operators as preprocessor macros via a header, C++ integrated them directly into the language's lexical grammar.

Under the C++ grammar specification, these alternative representations are recognized at the tokenization phase (lexical analysis) as identical to their symbolic counterparts. This means that in C++:

  • and is a direct synonym for &&
  • or is a direct synonym for ||
  • not is a direct synonym for !
  • bitand is a direct synonym for &
  • bitor is a direct synonym for |
  • xor is a direct synonym for ^
  • compl is a direct synonym for ~
  • and_eq, or_eq, xor_eq, and not_eq correspond to &=, |=, ^=, and !=

Because they are baked into the compiler's lexer, no header file needs to be included in C++ to use them. Furthermore, identifiers cannot use these names, preventing accidental name collisions in well-formed code. Although C++ retained a dummy <ciso646> (and later <version>) header for compatibility with C codebases, the header is empty in compliant C++ implementations because the tokens are already reserved keywords.

Modern Status and Usage

Today, UTF-8 and modern keyboard layouts have made the original hardware-based rationale for alternative tokens largely obsolete. However, they remain standard C++ keywords for several reasons:

  • Backward Compatibility: Code written for or ported from legacy systems continues to compile without modification.
  • Readability Preferences: Some developers and code style guides prefer words like not and and over ! and && because they find them more readable or less prone to typographical errors (such as mistyping & instead of &&).
  • Tooling Differences: Compilers like GCC and Clang enforce these keywords natively by default. In contrast, the Microsoft Visual C++ (MSVC) compiler historically required either a conformance flag (such as /permissive- or /std:c++latest) or inclusion of <iso646.h> to enable them, though modern MSVC versions now support them natively in conforming modes.