Last chance to fix std :: nontype
- Document number:
- P3740R0
- Date:
2025-06-14 - Audience:
- LEWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P3740/github
- Source:
- github.com/Eisenwave/cpp-proposals/blob/master/src/nontype.cow
constructor which utilize a helper type
.
This helper type may be obsoleted by other active proposals,
and its name has become a bad choice now that C++26 no longer replaced the term
"non-type template parameter",
with "constant template parameter".
Contents
Introduction
Why did we need a new std :: nontype
type?
Refresher on what std :: nontype
accomplishes
Proposed solution
Comparison with std :: nontype
Note on free function ergonomics
Possible misuse of std :: stateless
Advantages compared to std :: nontype
Alternatives considered
Removing std :: nontype
with no replacement, revisiting for C++29
Waiting for constexpr
function parameters
Replacing std :: nontype
with std :: constant_wrapper
Exposing the BoundEntityType
Renaming std :: nontype
Conclusion
Implementation experience
Wording
Option A — Replacing std :: nontype
with a std :: stateless
tag
Option B — Renaming std :: nontype
to std :: constant_arg
References
1. Introduction
[P2472R3] proposed additional overloads for the
constructor which utilize a helper type
.
This paper was merged into [P0792R14],
which was plenary-approved at Varna 2023 for C++26.
There is now a proposal [P2781R8] on track for C++29 which adds a
helper type,
and this could be a plausible replacement for
.
Considering that, do we even need
?
Furthermore, the naming choice
makes very little sense
following [P2841R1], which introduced concept and variable template parameters.
Since those are not types either, what the standard used to call
"non-type template parameter" has been renamed to "constant template parameter".
In short, we now have to decide for C++26 whether
should be renamed (and if so, what to),
whether it should be merged with
,
or whether these constructor overloads should be delayed until C++29.
,
and the corresponding variable template is called
.
This document refers to the feature as a whole as
.
1.1. Why did we need a new std :: nontype
type?
An obvious question may be why the existing
cannot be used instead.
This has multiple reasons:
requires specifying the type separately.std :: integral_constant < class T , T v >
has a lengthy name.std :: integral_constant
has a call operator which returnsstd :: integral_constant
, which produces friction with the other constructors.v
1.2. Refresher on what std :: nontype
accomplishes
is used only within constructors of
:
Intuitively,
is the C++ counterpart to the C idiom of passing
and a function pointer which that
is fed into,
as seen in
.
It is extremely common practice to provide a null pointer to
and thus rely on a "capture-less" comparison.
With
,
can support such use "capture-less" uses:
Crucially, it would be impossible to create a
from a function directly without this helper.
At best, we could store function pointer within
2. Proposed solution
We can ditch
and obtain the functionality it provides in a different way.
This would be very tempting because we don't have the dilemma of naming it right,
making it redundant with
,
having a weird workaround that becomes obsolete with
function parameters, etc.
Instead, we can get the same functionality using a tagged constructor.
The crucial observation in §1.2. Refresher on what
accomplishes is that the lambda we create for
,
,
, etc.
We don't need to hold an instance of these; we can just default-construct when needed.
Specifically, the tagged constructor would function like:
2.1. Comparison with std :: nontype
|
|
---|---|
2.1.1. Note on free function ergonomics
While the construction from a regular function seems much more verbose with tagged constructors,
[P3312R1] "Overload Set Types" could make this substantially more concise.
Furthermore, it is common practice to create
macros for this purpose,
which would be used like:
Since most standard library functions aren't addressable anyway,
the ergonomics of creating a
directly from a free function
may not be that significant.
In fact, it could be argued that
encourages the user to write
possibly-ill-formed code like
by making it so frictionless.
2.2. Possible misuse of std :: stateless
This would invoke a value-constructed
when
is called,
resulting in undefined behavior.
This possible bug can be easily caught by adding deleted constructors though:
An even better option would be to add Mandates specifications
to the
constructors which require that the given callable
is not a function pointer type.
Although none of these fixes prevent the user from writing this mistake,
it can be caught statically with ease,
rather than actually resulting in undefined behavior.
2.3. Advantages compared to std :: nontype
The proposed approach has several advantages:
- Providing tag types within constructors is a more well-established design within the standard library.
-
The naming issue for
is eliminated completely. While the tag also needs a good name, we don't need to worry about conflicting withstd :: nontype
, which seems harder to solve.std :: constant_wrapper -
Potential user confusion over what the difference between
andstd :: nontype
is, and why the language needs both is, is eliminated.std :: constant_wrapper -
The design wouldn't feel immediately obsolete and deprecation-worthy
once/if we get
function parameters ([P1045R1]). It is worth noting thatconstexpr
does not require structural types, so there may be niche use cases where it could be used andstd :: stateless
couldn't.std :: nontype
3. Alternatives considered
Besides the proposed approach, there are other possible solutions. However, the author position is that every one of them is worse than what is proposed.
3.1. Removing std :: nontype
with no replacement, revisiting for C++29
Since there is a lot of active work in this area,
perhaps we could simply shove
into C++29 and deal with the problem later.
However, as demonstrated in §1.2. Refresher on what
accomplishes,
this helper type covers crucial use cases such as
- constructing
from a free function with no overhead, orstd :: function_ref - constructing
from an existing function pointer andstd :: function_ref
, in the style ofvoid *
.qsort
Author position:
The functionality provided by
is a crucial part of
,
not a nice-to-have feature that can be ripped out.
without these constructors would be akin
to using containers without move semantics.
3.2. Waiting for constexpr
function parameters
,
, and
are – to an extent – temporary hacks.
If we were able to write
… then the workaround of
would be obsolete.
At Kona 2023, EWG showed enthusiasm for this option
when discussing [P1045R1] "
Function parameters".
Poll: P2781R3 “std::constexpr_v” and P1045R1 “constexpr function parameters” EWG would like to solve th eproblem solved by std::constexpr_v in the language, for example as proposed by P1045R1 or with “expression aliases”, rather than solving it in library. An implementation is desired. C++26 seems ambitious.
SF F N A SA 6 8 5 1 0
However, [P1045R1] has not seen any seen any activity from the author since 2019.
As nice as a core language solution would be,
standard library features cannot be built on on hopes, dreams, and unimplemented hypotheticals.
Even if
function parameters were eventually implemented,
it may be possible to integrate them into the existing set of constructors
and to deprecate
.
Author position:
Overall, there is little reason to treat
function parameters
as a prerequisite; we don't need to wait.
3.3. Replacing std :: nontype
with std :: constant_wrapper
The seemingly obvious solution is to use
from [P2781R8].
The option to use
as a replacement for
was discussed during an LEWG telecon 2025-03-11.
Major concerns have been raised over the fact that
is substantially more complex than
:
-
has anstd :: constant_wrapper
already. If we have, say,operator ( )
made from somestd :: cw < & f >
, calling this wrapper would give us aint f ( )
convertible tostd :: constant_wrapper
. This means that it could also be passed into the constructor of aint
taking some callable typestd :: function_ref < int ( ) >
.F && -
also has a user-defined conversion function which returns the constant it wraps. If it wraps a function pointer (and this is a desired use case forstd :: constant_wrapper
), this would also make thestd :: nontype
constructor takingstd :: function_ref
(i.e. the function pointer constructor) a candidate. However, that candidate would always lose in overload resolution, so this is not a major concern.F * -
would only treated specially bystd :: constant_wrapper
, but due to the behavior described in the previous bullets, it could also be passed into other "function wrappers" likestd :: function_ref
. This could easily confuse users who may expect thatstd :: move_only_function
is a general mechanism for wrapping functions passed to such "function wrappers".std :: function_ref
However, even with these major concerns,
it would not be impossible to use
for this.
The
and
constructors should be losing in overload resolution anyway.
Even if they didn't, the overload set could be modified to resolve ambiguity.
Author position:
Considering the call operator and user-defined conversion functions of
, the type is not a good fit for
construction.
3.4. Exposing the BoundEntityType
Remember the example in §1.2. Refresher on what
accomplishes.
Our end goal is to enable more possible constructions of
.
Technically, this could be done by ripping the implementation open and exposing its details:
However, this means that the user directly interacts with
,
,
but this may also be a
, it may be multiple pointers large, etc.
Author position:
Exposing the
3.5. Renaming std :: nontype
Perhaps the most simple solution is to simply rename
.
However, no good candidate with high consensus has been presented yet,
and many options would be awfully similar to
,
creating confusion:
-
seems almost like a "more official" alternative tostd :: constant
, when this type is extremely limited on scope.std :: constant_wrapper -
,std :: ctp
,std :: cta
and other abbreviations derived from "constant template argument/parameter" feel out-of-place in the standard library, and cannibalizestd :: ct
, the variable template forstd :: cw
.std :: constant_wrapper -
,std :: const_arg
,std :: constant_arg
and other such names describe the purpose of the type well, but are still reminiscent ofstd :: constexpr_arg
.std :: constant_wrapper
Author position:
Renaming
is not trivial,
and it's unlikely that any solution will make everyone happy.
Of all the options,
seems like the least bad candidate.
3.6. Conclusion
Every alternative seems bad in its own way,
but §3.5. Renaming
seems least intrusive,
and is definitely something that we could do for C++26.
In §5. Wording, both the wording for §2. Proposed solution
and for §3.5. Renaming
is presented.
4. Implementation experience
§2. Proposed solution has been implemented at [GitHub]. This was done by forking the existing reference implementation of [P0792R14].
No issues whatsoever were encountered in the process;
either solution provides the same functionality for the most part,
although
does provide more direct support for function pointers
and member pointers.
5. Wording
The following changes are relative to [N5008].
5.1.
Option A — Replacing std :: nontype
with a std :: stateless
tag
In [version.syn], update the feature-test macro:
In [utility.syn],
delete the declarations of
and
.
In its place, insert the following declaration:
Change [func.wrap.ref.class], as follows:
Change [func.wrap.ref.ctor] as follows:
9 Let
be
.
10
Constraints:
is
.
11
Mandates:
If
is
,
then
is
.
is
.
12
Effects:
Initializes
with a pointer to an unspecified object or
null pointer value, and
with the address of a function
such that
is expression-equivalent ([defns.expression.equivalent]) to
.
13
Let
be and
.
be
14 Constraints:
isis_rvalue_reference_v < U && >
,false and
isis-invocable-using < F , cv T & > true ., and
isis_default_constructible_v < F >
.true
15
Mandates:
If
is
,
then
is
.
is
.
16
Effects:
Initializes
with
, and
with the address of a function
such that
is expression-equivalent ([defns.expression.equivalent]) to
.
17
Let
be
.
18
Constraints:
is
.
19
Mandates:
If
is
,
then
is
.
is
.
20
Preconditions:
If
is
,
is not a null pointer.
21
Effects:
Initializes
with
, and
with the address of a function
such that
is expression-equivalent ([defns.expression.equivalent]) to
.
In [func.wrap.ref.deduct], delete the following deduction guides:
2
Let
be
.
3
Constraints:
is
.
4
Remarks:
The deduced type is
.
5
Let
be
.
6 Constraints:
-
is of the formF
for a typeR ( G :: * ) ( A ... ) cv & opt noexcept ( E )
, orG -
is of the formF
for a typeM G :: *
and an object typeG
, in which case letM
beR
,invoke_result_t < F , T & >
be an empty pack, andA ...
beE
, orfalse -
is of the formF
for a typeR ( * ) ( G , A ... ) noexcept ( E )
.G
7
Remarks:
The deduced type is
.
5.2.
Option B — Renaming std :: nontype
to std :: constant_arg
In [version.syn], update the feature-test macro:
In [utility.syn], modify the header synopsis as follows:
In [func.wrap.ref.class],
replace every occurrence of
with
:
In [func.wrap.ref.ctor],
replace every occurrence of
with
:
[…]
[…]
[…]
[…]
21 Constraints:
is not the same type asT
,function_ref
isis_pointer_v < T >
, andfalse
is not a specialization ofT nontype_tconstant_arg_t.
In [func.wrap.ref.deduct],
replace every occurrence of
with
:
[…]
[…]
[…]