1. Motivation
Conditional
specifications can be easy to write.
template < Movable T > T example1 ( T && ) noexcept ; template < Movable T > T example2 ( T && ) noexcept ( is_nothrow_move_constructible_v < T > and is_nothrow_move_assignable_v < T > );
They can also be painful to write, and thus painful to read. Consider writing a
specifier
for
:
template < InputIterator I , Sentinel < I > S , class T , class Proj = identity > requires IndirectRelation < I , T * , ranges :: equal_to , Proj > I find ( I first , S last , const T & val , Proj proj = {}) noexcept ( is_nothrow_move_constructible_v < I > and is_nothrow_move_assignable_v < I > and is_nothrow_copy_constructible_v < I > and is_nothrow_copy_assignable_v < I > and is_nothrow_destructible_v < I > and is_nothrow_move_constructible_v < S > and is_nothrow_move_assignable_v < S > and is_nothrow_copy_constructible_v < S > and is_nothrow_copy_assignable_v < S > and is_nothrow_destructible_v < S > and noexcept ( first != last ) and noexcept ( ++ first ) and noexcept ( * first ) noexcept ( * first == val ) ) { for (; first != last ; ++ first ) { if ( * first == val ) { break ; } } return first ; }
The above
-specifier should capture all the expressions in the algorithm (although it is
entirely possible that the author "honestly" missed something, which should serve as further
motivation for this proposal).
That’s a lot to read, and since iterators are broadly used in the standard library, it might make
more sense for us to create
template variables à la
, but these
will largely be repeating the definition of a concept. Take
for example:
template < Semiregular T > constexpr is_nothrow_semiregular = is_nothrow_default_constructible < T > and is_nothrow_copyable < T > ;
We’re now beholden to defining
and
, and we
can’t define
as
, since
it would not check that the type’s destructor is non-throwing. Similarly,
would need to be defined such that it checks
's copy constructor, move constructor, and
respective assignment operators don’t throw, in addition to the destructor (if something is truly
no-throw copyable, it should most probably have a non-throwing destructor too). At this point, it
looks as though we might be considering redefining all the concepts introduced in C++20 for
-specifiers.
template < InputIterator I , Sentinel < I > S , class T , class Proj = identity > requires IndirectRelation < I , T * , ranges :: equal_to , Proj > constexpr I find ( I first , S last , T const & value , Proj proj ) noexcept ( is_nothrow_input_iterator < I > and is_nothrow_sentinel < S , I > and is_nothrow_indirect_relation < I , T * , ranges :: equal_to , Proj > );
This is most certainly repeating the interface unnecessarily: we have all of the information we need
in the form of concepts. PXXXX seeks to introduce
-- or some other spelling —
is
equivalent to
if, and only if, all expressions in all (transitive) requirements are
also.
// Example 1 class nothrow_example { public : nothrow_example () = default ; constexpr explicit nothrow_example ( int const value ) noexcept : value_ { value } {} constexpr int size () const noexcept { return value_ ; } private : int value_ = 0 ; }; template < Semiregular T > auto possibly_nothrow ( T t ) noexcept ( requires ) { return t . size () * 2 ; } int main () { possibly_nothrow ( nothrow_example { 42 }); // is noexcept possibly_nothrow ( vector < int > ( 1 ’000 ’000 )); // is not noexcept }
In Example 1,
is deemed to be a
function
because none of its operations potentially throw.
is potentially
throwing due to its copy operations being potentially throwing.
// Example 2 class mostly_nothrow { public : mostly_nothrow () = default ; constexpr explicit mostly_nothrow ( int const value ) : value_ { value } {} constexpr int size () const { return value_ ; } private : int value_ = 0 ; }; // ... possibly_nothrow ( mostly_nothrow { 42 });
In Example 2,
is also a
function,
because the interface specified through the defined concepts do not check for
.
Just as we would get a template instantiation error if we tried to call
due to
not existing,
slips through the cracks because its
potentially-throwing constructor is not checked in any way.
// Example 3 class actually_throws { public : actually_throws () = default ; constexpr explicit actually_throws ( int const value ) noexcept : value_ { value } {} constexpr void size () const { throw value_ ; } private : int value_ = 0 ; }; // ... possibly_nothrow ( actually_throws { 42 });
In Example 3,
is also
for the same reasons as
in Example 2.
// Example 4 template < class T > auto possibly_nothrow ( T const & t ) noexcept ( requires ) { return t . size (); }
Example 4 could either be equivalent to
or it could be ill-formed, as this
is a completely unconstrained overload of
. The author has no strong opinion on
which direction to take.
1.1. Before-and-after tables
C++20 | C++23 |
---|---|
|
|
2. Presumed criticisms
2.1. This is a rebranding of noexcept ( auto )
Through online chatter, the author is of the opinion that
has become a loaded term
to mean something along the lines of "check all expressions in a function definition and only if all
of them are
, then this function is
also".
only focuses on
the expressions required by the function template declaraiton.
2.2. Are you serious about the spelling!?
This proposal makes no intention to word-smith.
was chosen because it couples
the idea of
and requires-expressions for communication with EWG. The author
encourages bikeshedding a different spelling, but strongly adivses against spelling this language
feature as
.
2.3. If you’re confident that this isn’t noexcept ( auto )
, then it’s broken, because...
... a concept might contain features that a function doesn’t use. e.g.
// Let SequenceContainer roughly represent the named requirement SequenceContainer from // [container.requirements.general] Table 62 // void clear ( SequenceContainer auto & c ) noexcept ( requires ) { c . clear (); }
Java provides the language feature
to dictate what the interface of a given class looks
like. For those unfamiliar with Java, an
prescribes a type’s interface, dictating the
minimum number of member functions that a class conforming to the interface must provide; otherwise
it is an abstract class. The closest that C++ has to offer here are virtual functions.
It is not the primary purpose of concepts to mimic Java’s
: it is to expres requirements
on type parameters through concepts is to describe the minimal interface needed for
well-formed usage. This is why the range-based
requires an
, not a
. The fact that concepts can be used similarly to Java’s
is a happy
coincidence. The author suspects that programmers using concepts as a mechanism for
is
likely inevitable, but the C++ spelling for
is
, not
. As
such, the author deems that the
function provided does not fall within the design-space of
concept usage, and is not a valid counter-example.
...
refines
InputIterator and the copy happens outside the function call.
Copyable
[P1207] has been forwarded to LWG for review.
3. Acknowledgements
The author would like to thank Isabella Muerte and Morris Hafner for their feedback on this proposal.