Document #: | P2984R1 |
Date: | 2023-11-12 |
Project: | Programming Language C++ |
Audience: |
Evolution (information only) |
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; it concludes with the Evolution Working Group
review that recommends no changes for C++26, but encourages compiler
vendors to increase the prominence of warnings to better inform the
discussion when this process is repeated for C++29.
This revision is informative only, recording the EWG review process that ends the progress of this paper. Tentatively closing this document to future work.
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, [P2863], 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.
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.
The main four compiler front ends were tested at Godbolt with the following 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)
Most current compilers can warn on the use of this deprecated
feature, but the deprecation warning is not enabled with just the common
command line switches. Specifically, neither GCC nor Clang will enable
this warning with either -Wextra
or -Wall
, but require the more
obscure -Wdeprecated
. Clang also
supports this diagnostic with the extremely noisy
-Weverything
. Likewise,
Microsoft Visual C++ does not enable this diagnostic at the notionally
highest warning level of /W4
,
but requires the extra noisy warning mode of
Wall
. To our best knowledge, the
EDG front end does not warn about this usage yet.
Compiler
|
Warn since
|
Compiler switch
|
---|---|---|
Clang | 2022/09/06 | Wdeprecated |
GCC | 2017/05/02 | Wdeprecated |
MSVC | 2022/07/19 | Wall |
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
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.
The different paths to make progress were considered, and there was much interest in the deprecation warnings, and how they might be made more visible to our users.
Informally, the room seemed more in favor of removing this feature than undeprecating it, but there is no desire to make a potentially breaking change without a clear signal to our users to address their code first.
After polling, there was a strong consensus to make no changes for C++26, i.e., retain the status quo. We encourage the compiler vendors to raise the presence of the deprecation warning so that we have both more visibility and more data regarding the impact on user code for the next time we review this feature.
After reviewing the possible directions, the recommendation of the
Evolution Working Group is to make no changes to the C++26 Standard.
However, there was a desire to encourage compiler vendors to raise raise
the visibility of this particular deprecation warning so that it would
be reported by more frequently used warning flags, such as
-Wextra
or even
-Wall
. There is a desire to
remove this deprecated syntax in a future standard, but users need more
visibility of the deprecation warning, and EWG need more data about how
much code might be impacted when we review this feature again for
C++29.
Thanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.
Thanks to Corentin Jabot for explaining the
Weverything
and
Wdeprecated
flags that are
needed to expose the deprecation warning in current compilers.