Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
33 changes: 22 additions & 11 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,9 +22,14 @@ 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.

For example, the table below shows pseudo-signatures for a *sortable* type ``T``:
Expand All @@ -43,12 +48,19 @@ For example, the table below shows pseudo-signatures for a *sortable* type ``T``

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

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.
A pseudo-signature describes how an implementation interacts with a type or a function.
A real 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: its function parameter types need to implicitly convert
from the ones in the pseudo-signature, and the return value type needs to implicitly convert to the one
in the pseudo-signature.

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 ``const U&`` to ``U`` if the type is copy-constructible.
For a counter-example, the real signature ``bool operator<( U& x, U& y )`` is not acceptable
because C++ does not permit implicit removal of a ``const`` qualifier from a type, and so the code
would not compile if the implementation attempts to pass a const object to the function.
Copy link
Contributor

Choose a reason for hiding this comment

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

May be it makes sense to also explicitly mention that if pseudo-signature declares return type as void, but the real signature returns some type, the returned value should be ignored by the implementation.


Algorithms
----------
Expand Down Expand Up @@ -81,7 +93,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 an object ``c`` of type (possibly ``const``) ``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 @@ -15,46 +15,28 @@ It should also meet one of the following requirements:

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

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

Process the received item.

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

Choose a reason for hiding this comment

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

Should it be ReferenceType as well?

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

Copy link
Contributor Author

@akukanov akukanov Oct 7, 2025

Choose a reason for hiding this comment

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

This question is related to the question below about "removing" a requirement:

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.

As I understand it, it says that if the feeder is used, the body has to accept rvalues - because the work elements supplied through the feeder will be "moved" to the body for processing. Since parallel_for_each may only use one operator(), I interpret the requirement as "if your body operator takes a feeder, it has to accept rvalues" - and that is exactly what the pseudo-signature describes.

If the idea is that the original sequence elements are passed just as the result of iterator dereferencing (i.e., *it) while new work elements are "moved" - the operator should then support both ReferenceType (which might be an lvalue reference) and ItemType&& rvalue reference - so it seemingly should take arguments either by value or by const reference. Neither allows modification of the original sequence (which can be fine, given the use of feeder); taking by value will have some copy construction overhead, and taking by const reference makes move useless.

Another option is to use a templated operator with universal references or use different overloads for lvalues and rvalues.

Let's figure out what semantics we want, how it could be implemented in practice, and then hopefully it will be more clear how to describe that in the named requirement.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the original idea was that the elements of the input sequence are passed as the result of dereferencing. and additional items are always provided as rvalues.

The semantics proposed would break the first part, for example if the iterator have non-const lvalue reference, *it will not be accepted by the body. And the opposite is true, if the operator accepts non-const lvalue reference argument, passing of new items through feeder would not work.

I disagree that parallel_for_each may use only one operator(). I think it should be possible to have several overloads with different first argument types for input sequence and feeder items. By the way, current implementation of oneTBB would also work if the input sequence dereference is passed to the overload with feeder and the additional items are passed to the overload without the feeder:

struct body {
    // overload for feeder items
    void operator()(ItemType&& value_from_feeder) const {}
    
    // overload for input sequence
    void operator()(ItemType& input_sequence, oneapi::tbb::feeder<ItemType>& feeder) const {}
};

Copy link
Contributor

Choose a reason for hiding this comment

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

This document specify that the body should meet one of the requirements - either be invocable without a feeder or with it.
Should it be specified/recommended which overload should be preferred if both of them are present.
oneTBB implementation prefers the overload with the feeder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think it should be specified. I do not see how the implementation could use both overloads; it would need to decide whether to provide the feeder or not, and how would it decide? So using the overload with the feeder all the time is the "safest" choice.


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

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

.. note::

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

Terms
-----

* ``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``.
where ``ItemType`` is ``std::iterator_traits<InputIterator>::value_type`` for the type of the iterator
the ``parallel_for_each`` algorithm operates with, and ``ReferenceType`` is

``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.
* ``std::iterator_traits<InputIterator>::reference`` if the iterator type is a forward iterator
as described in the [forward.iterators] section of the ISO C++ Standard;
* otherwise, ``ItemType&&``.

`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

.. caution::

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.
Comment on lines -56 to -57
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it intentional that this requirement is removed?

The usual rules for :ref:`pseudo-signatures <pseudo_signatures>` apply.
Therefore, ``Body::operator()`` may optionally take ``ItemType`` by value.
``const`` and ``volatile`` type qualifiers are also applicable.

See also:

Expand Down
Loading