Document #: | P2984R0 |
Date: | 2023-09-27 |
Project: | Programming Language C++ |
Audience: |
Library Evolution |
Reply-to: |
Alisdair Meredith <ameredith1@bloomberg.net> |
With the introduction of inline variables in C++17,
static constexpr
data members
are defined by their in-class declarations and the legacy out-of-class
definitions became redundant redeclarations and were deprecated. This
paper examines the feasibility of removing support for the deprecated
redeclarations, and whether undeprecation would be the better
forward-looking policy.
At the start of the C++23 cycle, [P2139R2] tried to review each deprecated feature of C++, to see which we would benefit from actively removing, and which might now be better undeprecated. Consolidating all this analysis into one place was intended to ease the (L)EWG review process, but in return gave the author so much feedback that the next revision of that paper was not completed.
For the C++26 cycle there will be a concise paper tracking the overall review process, [P2863R2], but all changes to the standard will be pursued through specific papers, decoupling progress from the larger paper so that delays on a single feature do not hold up progress on all.
This paper takes up the deprecated redeclaration of
constexpr
data members,
D.6
[depr.static.constexpr].
constexpr
in C++11The first C++ Standard to support
static constexpr
data members
was C++11, with the introduction of the
constexpr
keyword.
Definitions for
static constexpr
data members
outside the enclosing class definition became redundant redeclarations
with the application of [P0386R2] for C++17,
inline variables. Such redeclarations were consequently deprecated,
although considered harmless. There has been no further progress on this
topic in the last seven years.
At the Varna meeting in 2023, the deprecation of D.6 [depr.static.constexpr] was presented in the context of maintaining status quo, as presented in paper P2863R0.
While there was unanimous agreement that removal from C++26 seemed a bad idea, there was interest in soliciting a paper to propose undeprecation.
Poll: EWG is interested in un-deprecating defining
inline constexpr
class variables
(P2863R0 section 6.6).
SF F N A SA 2 12 7 3 0
Result: Consensus to request a paper, which you are now reading.
According to 9.2.6
[dcl.constexpr],
static constexpr
data members of
a class are implicitly defined as inline variables:
3 A function or static data member declared with theconstexpr
orconsteval
specifier is implicitly an inline function or variable.
According to 6.2 [basic.def], inline static data member declarations are also definitions (by inference of not being excluded), and the following bullets clarify that further declarations outside the class are just that, declarations and not definitions.
2 Each entity declared by a declaration is also defined by that declaration unless:
(2.3) — it declares a non-inline static data member in a class definition (11.4 [class.mem], 11.4.9 [class.static]),
(2.4) — it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (11.4.9.3 [class.static.data]) (this usage is deprecated; see D.6 [depr.static.constexpr]),
When considering the removal of redundant definitions, it seems simple enough to support a code base that is common with C++11 and the feature removal (or warned deprecation) with a simple feature check:
C++11 and C++14
|
Portable with no deprecation
|
---|---|
|
|
Note that the Portable code works both before and after C++17, and whether or not the redundant redeclaration is deprecated (removing a warning) or ill-formed if support for redeclarations were removed from a future standard. Once a codebase establishes that its minimum supported dialect of C++ is C++17 or later the conditionally translated code can be removed completely.
As of writing this paper in September 2023, none of the 4 major compiler front ends report a deprecated-use warning on the example in the standard, including the latest trunk build for open source compilers.
Example code from D.6 [depr.static.constexpr]:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
All the latest compiler available through Godbolt compiler explorer
were tested with -Wall -Wextra
except MSVC which was tested with
\W4
. Language dialect tested was
C++20, as that is a common base for all compilers, and this feature has
been deprecated since the earlier C++17.
Compiler
|
Warn since
|
---|---|
Clang | no warnings |
GCC | no warnings |
MSVC | no warnings |
nvc++/EDG | no warnings |
We might consider several directions to make progress for C++26.
Status quo prevails if we do not make a persuasive enough argument to make a change. However, in this case there is an argument to be made explicitly in favor of the status quo.
To maintain maintain a simpler language in the long term, it would be nice to remove a corner case that permits redundant redeclarations where redeclarations are normally ill-formed. Removing corner cases that serve no benefit to the modern language avoids the accumulation of “cruft” that degrades the user experience in long-term ongoing project.
The workaround to make code portable across all C++ dialects is simple and amenable to tooling that can parse C++, such as through a fix-it hint from a compiler front end. If we retain the ambition to one day remove this deprecated feature, we should encourage compiler vendors to start diagnosing code that relies on it today. Such diagnostics have been relevant since 2017, and should be deployed without waiting for the release of the C++26 Standard.
If nothing else, retaining the deprecated status indicates that the feature is historical baggage and not an essential part of the language.
If we believe that this deprecated feature can never be removed, then
it would be an active disservice to our users for compilers and other
tools to start warning on deprecated usage. In such case, we should
actively consider undeprecating redundant redeclaration of
static constexpr
data
members.
Make the following changes to the C++ Working Draft, undeprecating
redundant redeclaration of
constexpr
data members outside
their class. All wording is relative to [N4958], the latest draft at the time of
writing.
2 Each entity declared by a declaration is also defined by that declaration unless:
(2.4) — it
declares a static data member outside a class definition and the
variable was defined within the class with the
constexpr
specifier
(11.4.9.3
[class.static.data])
(this usage is deprecated;
see D.6
[depr.static.constexpr]),
4
If a non-volatile non-inline
const
static data member is of
integral or enumeration type, its declaration in the class definition
can specify a brace-or-equal-initializer in which every
initializer-clause that is an assignment-expression is
a constant expression (7.7
[expr.const]). The
member shall still be defined in a namespace scope if it is odr-used
(6.3
[basic.def.odr]) in
the program and the namespace scope definition shall not contain an
initializer. The declaration of an inline static data member
(which is a definition) may specify a
brace-or-equal-initializer. If the member is declared with the
constexpr
specifier, it may be
redeclared in namespace scope with no initializer (this usage is deprecated; see D.6
[depr.static.constexpr]).
Declarations of other static data members shall not specify a
brace-or-equal-initializer.
No changes are needed for Annex C, as restoring deprecated functionality does not risk any breakage.
static constexpr
data members1
For compatibility with prior revisions of C++, a
constexpr static
data member may
be redundantly redeclared outside the class with no initializer
(6.2
[basic.def],
11.4.9.3
[class.static.data]).
This usage is deprecated.
[Example 1:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
}; constexpr int A::n; // redundant declaration (definition in C++ 2014)
—end example]
All clause and subclause labels from ISO C++ 2023 (ISO/IEC 14882:2023, Programming Languages — C++) are present in this document, with the exceptions described below.
container.gen.reqmts see
container.requirements.general
depr.res.on.required removed
depr.static.constexpr
removed
Given the lack of deprecation warnings issued at the time of writing,
6 years after the redundant redeclarations were formally deprecated in a
published standard, it seems unreasonable to recommend removal at this
time. Since the deprecated syntax was actually required by C++11 and
C++14 (prior to C++11 there was no
constexpr
keyword) it is likely
that a lot of current code would break upon removing this feature
without a transitional period where compilers do issue warnings about
the current deprecation.
That said, the workaround to make code portable across all C++ dialects is simple and amenable to tooling that can parse C++, such as through a fix-it hint from a compiler front end.
Make the following changes to the C++ Working Draft, making redundant
redeclaration of constexpr
data
members outside their class ill-formed. All wording is relative to [N4958], the latest draft at the time of
writing.
2 Each entity declared by a declaration is also defined by that declaration unless:
(2.4)
— it declares a static data
member outside a class definition and the variable was defined within
the class with the
,constexpr
specifier
(11.4.9.3
[class.static.data])
(this usage is deprecated; see D.6
[depr.static.constexpr])
4
If a non-volatile non-inline
const
static data member is of
integral or enumeration type, its declaration in the class definition
can specify a brace-or-equal-initializer in which every
initializer-clause that is an assignment-expression is
a constant expression (7.7
[expr.const]). The
member shall still be defined in a namespace scope if it is odr-used
(6.3
[basic.def.odr]) in
the program and the namespace scope definition shall not contain an
initializer. The declaration of an inline static data member
(which is a definition) may specify a
brace-or-equal-initializer. If the member is declared with the
.
Declarations of other static data members shall not specify a
brace-or-equal-initializer.constexpr
specifier, it may be redeclared in namespace scope with no initializer
(this usage is deprecated; see D.6
[depr.static.constexpr])
TBD
[Example 1:
struct A {
static constexpr int n = 5; // inline variable definition
}; constexpr int A::n; // ill-formed redeclaration in C++ 2026
—end example]
static constexpr
data members1
For compatibility with prior revisions of C++, a
constexpr static
data member may
be redundantly redeclared outside the class with no initializer
(6.2
[basic.def],
11.4.9.3
[class.static.data]).
This usage is deprecated.
[Example 1:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
}; constexpr int A::n; // redundant declaration (definition in C++ 2014)
—end example]
All clause and subclause labels from ISO C++ 2023 (ISO/IEC 14882:2023, Programming Languages — C++) are present in this document, with the exceptions described below.
container.gen.reqmts see
container.requirements.general
depr.res.on.required removed
depr.static.constexpr
removed
Thanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.