Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Requirements:
* If ``InputIterator`` type does not meet the `Forward Iterator` requirements from the [forward.iterators] section of the ISO C++ Standard,
the ``std::iterator_traits<InputIterator>::value_type`` type must be constructible from ``std::iterator_traits<InputIterator>::reference``.
* The ``Container`` type must meet the :doc:`ContainerBasedSequence requirements <../../named_requirements/algorithms/container_based_sequence>`.
* The type returned by ``Container::begin()`` must meet the same requirements as the ``InputIterator`` type above.
* The type returned by ``std::begin`` and ``std::end`` applied to a ``Container`` object
must meet the same requirements as the ``InputIterator`` type above.

The ``parallel_for_each`` template has two forms.

Expand Down
66 changes: 53 additions & 13 deletions source/elements/oneTBB/source/named_requirements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Named Requirements
This section describes named requirements used in the oneTBB Specification.

A *named requirement* is a set of requirements on a type. The requirements may be syntactic or semantic.
The *named_requirement* term is similar to “Requirements on types and expressions” term which is defined
by the ISO C++ Standard (chapter “Library Introduction”) or `“Named Requirements” section <https://en.cppreference.com/w/cpp/named_req>`_
on the cppreference.com site.
The *named requirement* term is similar to “Requirements on types and expressions” term which is defined
by the ISO C++ Standard (chapter “Library Introduction”) or
`“Named Requirements” section <https://en.cppreference.com/w/cpp/named_req>`_ on the cppreference.com site.

For example, the named requirement of *sortable* could be defined as a set of requirements that enable
an array to be sorted. A type ``T`` would be *sortable* if:
Expand All @@ -22,11 +22,22 @@ an array to be sorted. A type ``T`` would be *sortable* if:

You can write a sorting template function in C++ that sorts an array of any type that is *sortable*.

.. _pseudo_signatures:

Pseudo-Signatures
-----------------

Two approaches for defining named requirements are *valid expressions* and *pseudo-signatures*.
The ISO C++ standard follows the valid *expressions* approach, which shows what the usage pattern looks like for a requirement.
It has the drawback of relegating important details to notational conventions. This document uses
It has the drawback of relegating important details to notational conventions. This document mostly uses
pseudo-signatures because they are concise and can be cut-and-pasted for an initial implementation.

A pseudo-signature describes how an implementation interacts with a type or a function.
A real function signature (after template instantiation, if applicable) may differ from the pseudo-signature
that it implements in ways where implicit conversions would deal with the difference,
transforming function parameter types from the ones in the pseudo-signature to the real signature,
and transforming the actual return value type to the one in the pseudo-signature.

For example, the table below shows pseudo-signatures for a *sortable* type ``T``:

---------------------------------------------------------------------------------------------
Expand All @@ -35,20 +46,50 @@ For example, the table below shows pseudo-signatures for a *sortable* type ``T``

.. cpp:function:: bool operator<(const T& x, const T& y)

Compare x and y.
Compare ``x`` and ``y``.

.. cpp:function:: void swap(T& x, T& y)

Swap x and y.
Swap ``x`` and ``y``.

---------------------------------------------------------------------------------------------

A real signature may differ from the pseudo-signature that it implements in ways where implicit
conversions would deal with the difference. For an example type ``U``, the real signature that
implements ``operator<`` in the table above can be expressed as ``int operator<( U x, U y )``,
because C++ permits implicit conversion from ``int`` to ``bool``, and implicit conversion from ``U``
to (``const U&``). Similarly, the real signature ``bool operator<( U& x, U& y )`` is acceptable
because C++ permits implicit addition of a const qualifier to a reference type.
For a given type ``U``, the real signature that implements ``operator<`` in the table above
can be expressed as ``int operator<( U x, U y )``, because C++ permits implicit conversion from
``int`` to ``bool``, and implicit conversion from ``const U&`` to ``U`` if the type is copy-constructible.
As a counter-example, the real signature ``bool operator<( U& x, U& y )`` is not acceptable
because C++ does not permit implicit removal of the ``const`` qualifier from a type, and so the code
would not compile if the implementation attempts to pass a constant object to the function.

Besides pseudo-signatures, semantic requirements also need to be met by real types and functions.
For example, while ``std::pair<U, U> swap(U x, U y)`` fits the pseudo-signature for *Sortable*
via implicit conversion of references to values and implicit drop of the returned value
(ignored by a library implementation), it is unable to swap the actual variables passed to the function
and therefore does not meet the semantic requirements of *Sortable*.

The following table provides guidance for the types of parameters used in pseudo-signatures.

========================== ================================ =============================
Pseudo-signature parameter General semantics Alternative real parameters
========================== ================================ =============================
``const T& x`` The function is not supposed - ``T x``
to modify the argument. - ``U& x``
- ``U&& x``

where ``U`` is a template type parameter or ``auto``

``T& x`` The argument is a lvalue. - ``const T& x``
The function can or is - ``T x``
supposed to modify the argument. - ``U& x``
- ``U&& x``

``T&& x`` The argument is a rvalue. The - ``const T& x``
function can use the argument - ``T x``
as a source in move operations. - ``U&& x``
========================== ================================ =============================

In practice, suitable alternatives might depend on the semantic requirements as well as type properties,
such as availability of copy- or move-constructors.

Algorithms
----------
Expand Down Expand Up @@ -81,7 +122,6 @@ Mutexes

Containers
----------

.. toctree::
:titlesonly:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ ContainerBasedSequence
======================
**[req.container_based_sequence]**

A type `C` satisfies `ContainerBasedSequence` if it meets the following requirements:
A type `C` satisfies `ContainerBasedSequence` if the following expressions are valid
for a (possibly const) object ``c`` of the type `C`:

----------------------------------------------------------------

**ContainerBasedSequence Requirements: Pseudo-Signature, Semantics**

.. note::

In this page ``c`` is an object of type (possibly ``const``) ``C``.

Templates that use the named requirement can impose stricter requirements on the iterator concept.
**ContainerBasedSequence Requirements: Expression, Semantics**

.. cpp:function:: std::begin(c)

Expand All @@ -27,6 +22,13 @@ A type `C` satisfies `ContainerBasedSequence` if it meets the following requirem

Returns an input iterator one past the end of the sequence represented by ``c``.

----------------------------------------------------------------

.. note::

Templates that use `ContainerBasedSequence` may impose stricter requirements on the iterator type
returned by ``std::begin``/``std::end``.

See also:

* :doc:`parallel_for_each algorithm <../../algorithms/functions/parallel_for_each_func>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,44 @@ ParallelForEachBody
===================
**[req.parallel_for_each_body]**

A type `Body` satisfies `ParallelForBody` if it meets the `Function Objects`
requirements described in the [function.objects] section of the ISO C++ standard.
It should also meet one of the following requirements:
A type `Body` satisfies `ParallelForEachBody` if it meets the `Function Objects`
requirements described in the [function.objects] section of the ISO C++ standard,
as well as it meets exactly one of the following two requirements for ``operator()``:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to check my understanding: If I remember correctly, the necessity to satisfy exactly one requirement stems from the fact that it becomes ambiguous to the library implementation which one to call.

I wonder why the library cannot prefer one of the signatures if both exist? For example, is it possible to make it to prefer the one with the feeder?
I can imagine a use case with type hierarchy where instances of the base do not need to add additional work, while the derived classes may include more sophisticated processing. As far as I understand, the design where only one type of the signature can be present makes such inheritance impossible.

Also, perhaps, adding the word "alternative" helps in comprehension:

Suggested change
as well as it meets exactly one of the following two requirements for ``operator()``:
as well as it meets exactly one of the following two alternative requirements for ``operator()``:

Copy link
Contributor Author

@akukanov akukanov Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding the word "alternative" makes sense.

We can relax the requirements later if necessary, but we cannot make it stronger later without breaking compatibility.

For example, is it possible to make it to prefer the one with the feeder?

If the operator with a feeder is always preferred (which is the only reasonable choice really), why the one without the feeder is ever there? Also, the body can always ignore the feeder if it does not need to use it.

a use case with type hierarchy where instances of the base do not need to add additional work, while the derived classes may include more sophisticated processing. As far as I understand, the design where only one type of the signature can be present makes such inheritance impossible.

I do not get how a type hierarchy is relevant. The dereferenced iterator gives a very specific type. It can be a pointer-to-base, of course, but then the decision of using or not using the feeder is done at runtime, not at compile time. At compile time, the parameter of the operator should be either the base class or the derived class, but not both. If the processing is different, two different body types for parallel_for_each seemingly make more sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the one without the feeder is ever there?

I tried to come up with the example, where "the one without feeder" part cannot be easily changed, but whose implementation is desired to be reused.

but then the decision of using or not using the feeder is done at runtime, not at compile time.

I guess, unless the invocation routine is a template itself. Consider this code snippet: https://godbolt.org/z/qbx8eYMcP.

Not sure how realistic such example might be as the Derived::operator() could accept Derived& type just as well.


----------------------------------------------------------------

**ParallelForEachBody Requirements: Pseudo-Signature, Semantics**

.. cpp:function:: Body::operator()( ItemType item ) const
Alternative 1:

Process the received item.

.. cpp:function:: Body::operator()( ItemType item, oneapi::tbb::feeder<ItemType>& feeder ) const

Process the received item. May invoke the ``feeder.add(T)`` function to spawn additional items.

-----------------------------------------------------------------
.. cpp:function:: void Body::operator()( ReferenceType item ) const

.. note::
Process the received item.

``ItemType`` may be optionally passed to ``Body::operator()`` by reference.
``const`` and ``volatile`` type qualifiers are also applicable.
----------------------------------------------------------------

Terms
-----
Alternative 2:

* ``iterator`` determines the type of the iterator passed into the ``parallel_for_each`` algorithm,
which is ``decltype(std::begin(c))`` for the overloads that accept the ``Container`` template argument or ``InputIterator``.
* ``value_type`` - the type ``std::iterator_traits<iterator>::value_type``.
* ``reference`` - the type ``std::iterator_traits<iterator>::reference``.
.. cpp:function:: void Body::operator()( ReferenceType item, oneapi::tbb::feeder<ItemType>& feeder ) const
void Body::operator()( ItemType&& item, oneapi::tbb::feeder<ItemType>& feeder ) const

``oneapi::tbb::parallel_for_each`` requires the ``Body::operator()`` call with an object of the ``reference`` type to be well-formed if
the ``iterator`` meets the `Forward iterator` requirements described in the [forward.iterators] section of the
ISO C++ Standard.
Process the received item. May invoke the ``feeder.add`` function to spawn additional items.
The ``Body::operator()`` must accept both ``ReferenceType`` and ``ItemType&&`` values as the first argument.

`oneapi::tbb::parallel_for_each algorithm <../../algorithms/functions/parallel_for_each_func>`_
requires the ``Body::operator()`` call with an object of type ``const value_type&`` or ``value_type&&`` to be well-formed if following requirements are met:
-----------------------------------------------------------------

* the iterator meets the `Input iterator` requirements described in the [input.iterators] section of the ISO C++ Standard
* the iterator does not meet the `Forward iterator` requirements described in the [forward.iterators] section of the ISO C++ Standard
where

.. caution::
* ``ItemType`` is ``std::iterator_traits<Iterator>::value_type`` for the type of the iterator
the ``parallel_for_each`` algorithm operates with, and
* ``ReferenceType`` is ``std::iterator_traits<Iterator>::reference`` if the iterator type is
a `forward iterator` as described in the [forward.iterators] section of the ISO C++ Standard,
* otherwise, ``ReferenceType`` is ``ItemType&&``.

If the ``Body`` only takes non-const lvalue reference to the ``value_type``, the requirements described above
are violated, and the program can be ill-formed.
.. note::

Additional elements submitted into ``oneapi::tbb::parallel_for_each`` through the ``feeder::add`` are passed to the ``Body`` as rvalues. In this case, the corresponding
execution of the ``Body`` is required to be well-formed.
The usual rules for :ref:`pseudo-signatures <pseudo_signatures>` apply.
Therefore, ``Body::operator()`` may optionally take items by value or by ``const`` reference.

See also:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
==================
ParallelReduceFunc
==================
**[req.parallel_reduce_body]**
**[req.parallel_reduce_func]**

A type `Func` satisfies `ParallelReduceFunc` if it meets the following requirements:

-----------------------------------------------------------------------------------------------------

**ParallelReduceFunc Requirements: Pseudo-Signature, Semantics**

.. cpp:function:: Value Func::operator()(const Range& range, const Value& x) const
.. cpp:function:: Value Func::operator()(const Range& range, Value&& x) const

Accumulates result for a subrange, starting with initial value ``x``.
``Range`` type must meet the :doc:`Range requirements <range>`.
``Value`` type must be the same as a corresponding template parameter for the
:doc:`parallel_reduce algorithm <../../algorithms/functions/parallel_reduce_func>` algorithm.
Accumulates values over a subrange, starting with the initial value ``x``.
The ``Range`` type must meet the :doc:`Range requirements <range>`.
The ``Value`` type must be the same as the corresponding template parameter for the
:doc:`parallel_reduce algorithm <../../algorithms/functions/parallel_reduce_func>`.

See also:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ A type `Reduction` satisfies `ParallelReduceReduction` if it meets the following

**ParallelReduceReduction Requirements: Pseudo-Signature, Semantics**

.. cpp:function:: Value Reduction::operator()(const Value& x, const Value& y) const
.. cpp:function:: Value Reduction::operator()(Value&& x, Value&& y) const

Combines results ``x`` and ``y``.
``Value`` type must be the same as a corresponding template parameter for the
:doc:`parallel_reduce algorithm <../../algorithms/functions/parallel_reduce_func>` algorithm.
Combines the results ``x`` and ``y``.
The ``Value`` type must be the same as the corresponding template parameter for the
:doc:`parallel_reduce algorithm <../../algorithms/functions/parallel_reduce_func>`.

See also:

Expand Down
Loading