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++:
andis a direct synonym for&&oris a direct synonym for||notis a direct synonym for!bitandis a direct synonym for&bitoris a direct synonym for|xoris a direct synonym for^complis a direct synonym for~and_eq,or_eq,xor_eq, andnot_eqcorrespond 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
notandandover!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.