Case ranges
- Document number:
- D4040R0
- Date:
2026-02-27 - Audience:
- SG22
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P4040/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/case-ranges.cow
labels,
such as .
Such case ranges
have also been supported as a C++ compiler extension for many years.
This feature should be standardized for C++.
Contents
Introduction
Motivation
Design
Design strategy
Scoped enumerations
Why not wait for pattern matching?
What about pack expansion case labels?
Implementation experience
Wording
[cpp.predefined]
[stmt.label]
[stmt.switch]
References
1. Introduction
In 2024, [N3370] added support for ranges to C2y.
For example,
the following two statements are equivalent:
| C++26 | C2y N3370 |
|---|---|
|
|
|
The behavior here is obvious;
specifies ten cases in bulk.
This feature is already implemented in GCC and Clang, not just for C but also for C++. Since it is useful and widely supported, we should standardize existing practice and make it available to users in standard C++.
2. Motivation
The benefit of the range syntax is obvious: when there are several contiguous cases, it is much more concise than listing each case individually.
While it would also be possible to handle case ranges using an statement,
this often requires splitting off some cases from the .
This often results in a pattern like:
There is an obvious asymmetry here, decreasing readability.
In addition to the feature being generally useful in C++,
as mentioned above,
it is an existing C2y feature.
Providing it to users would make it easier to port C code to C++ and vice versa.
Historically, C++ has always provided the control flow constructs of C,
although may likely result in divergence.
3. Design
3.1. Design strategy
The overall strategy is to copy the semantics of the C2y feature as accurately as possible. Among other things, this includes design decisions such as:
-
is permitted. That is, mixing types in case ranges is allowed. However, just like for existingcase 0 ... 1 ll labels, this does not permit narrowing conversions (to the type of the condition).case A possible motivating example for mixing types in case ranges is: case 0 ... std :: numeric_limits < std :: uint8_t > :: max ( ) : Especially in generic code, it may be annoying if the types of both sides of the
constant-range-expression have to be of the same type. -
Overlapping ranges are disallowed.
That is, the
needs to be able to select oneswitch label unambiguously.case -
Empty case ranges such as
are valid, but recommended to be diagnosed.case 0 ... - 1 -
Both ends are inclusive.
That is,
includes the values 0 and 1, and can be expressed as the range [0, 1] in mathematical notation.case 0 ... 1
There is no obvious reason to deviate from the existing semantics of the C2y feature and of the C++ compiler extension; all these choices seem adequate.
A noteworthy quirk of the C2y feature is that a
is not valid because is parsed as a token with spaces,
which works around the problem.
3.2. Scoped enumerations
One feature exclusive to the C++ compiler extension is the support for scoped enumerations, such as in:
This part should also be standardized because it is useful.
Many scoped enumerations organize enumerations into blocks
,
such as success statuses and error statuses,
and case ranges allow for selecting such blocks.
,
but takes place in terms of the underlying type of the enumeration.
3.3. Why not wait for pattern matching?
Pattern matching provides very similar functionality:
| P2688R4 | C2y |
|---|---|
|
|
|
Nonetheless, the case ranges are worth considering for C++29 for a variety of reasons:
-
Case ranges are more concise.
To be fair, a similar
' 0 ' ... ' 9 ' match-case-pattern could also be added to pattern matching, which closes that gap. -
can be used in ways that pattern matching doesn't allow, like being nested inside other statements or with fallthrough into subsequent cases. Such uses are rare, but do exist.case - Case ranges can be used in code that is meant to compile in both C and C++, which is unlikely to ever be the case for pattern matching.
- Similarly, case ranges make it easier to port from C code that uses them to C++ code, and vice versa.
-
Standardizing case ranges
legitimizes
existing C++ code that relies on the GNU extension for case ranges by giving it behavior specified by the standard. This increases portability of old code bases that won't be modernized to use pattern matching.
3.4. What about pack expansion case labels?
It may be worth considering a pack expansion label, like:
However, this is not proposed, and is an entirely separate feature.
The only thing it has in common with the proposed syntax is the
.
Pack expansion labels are also not strictly more general;
this proposal offers ,
and doing the same via pack expansion would require a pack
well past any compiler limits.
Adding case ranges also does not make it impossible to add pack expansion cases later;
pack expansions use
as a unary suffix operator,
not as a binary operator.
would be disambiguated as an unexpanded pack on the left
side of a
This is fine because interpreting as a pack expansion
would make the construct as a whole invalid.
Therefore, no change in meaning takes place,
there pack expansions are supported or not.
In conclusion, pack expansion labels are not proposed,
and case ranges do not prevent such a feature from being added in the future.
4. Implementation experience
Case ranges have been first implemented in GCC 2.0 (1992) and Clang 1.0 (2007), albeit as a GNU extension, not as a standard C2y feature. Both GCC and Clang currently provide the feature as proposed in both C and C++ mode.
MSVC does not support case ranges.
5. Wording
The changes are relative to [N5032].
[cpp.predefined]
Add a feature-test macro to the table in [cpp.predefined] as follows:
[stmt.label]
Change [stmt.label] as follows:
A label can be added to a statement or used anywhere in a
label :- attribute-specifier-seqopt identifier
: - attribute-specifier-seqopt
constant-expressioncase : - attribute-specifier-seqopt
constant-range-expressioncase : - attribute-specifier-seqopt
default : labeled-statement :- label statement
constant-range-expression :- constant-range-expression
constant-range-expression...
[…]
[stmt.switch]
Change [stmt.switch] paragraph 2 as follows:
2 If the
3 Any statement within the statement can be labeled
with one or more case labels as follows
of one of the forms:
- attribute-specifier-seqopt
case constant-expression: - attribute-specifier-seqopt
case constant-range-expression:
where the switch condition.
Let the value range of a label be:
No two of the
case constants in the same switch
value ranges of labels associated with the same statement
shall have the same value after conversion overlap.
Attach an example to the previous paragraph (now paragraph 3):
[Example:
- 1 unsigned int — end example]
Immediately following [stmt.switch] paragraph 2 (now split into two paragraphs), insert a new paragraph:
Recommended practice:
Implementations should emit a warning
when the value range of a label is empty.
Change [stmt.switch] paragraph 3 as follows:
There shall be at most one label of the form
- attribute-specifier-seqopt
default :
within associated with a statement.
Change [stmt.switch] paragraph 4 as follows:
Switch statements can be nested;
a or label is associated with associated with
the smallest switch statement enclosing it.
Change [stmt.switch] paragraph 5 as follows:
When the statement is executed,
its condition is evaluated.
If one of the case constants has the same value as the condition,
control is passed to the statement following the matched
, and control may be passed
to one of the statements labeled by a label associated with the label.
If no case constant matches the condition,
and if there is a label,
control passes to the statement labeled by the default label.
If no case matches and if there is no
then none of the statements in the switch is executed.
statement,
selected as follows:
-
If the value of the condition lies in the value range of one of the
labels, control is passed to the statement labeled by that label. If the value is of enumeration typecase , determining whether it lies in the value range takes place by first explicitly converting ([expr.type.conv]) the value range boundaries and the value of the condition to the underlying type ofE .E -
Otherwise, if there is a
label, control is passed to the statement labeled by that label.default -
Otherwise, control is not passed
to any of the statements in the
statement.switch