Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of for_each in the presence of exceptions? std::exception_list

cppreference documentation https://en.cppreference.com/w/cpp/algorithm/for_each says that:

  • If execution of a function invoked as part of the algorithm throws an exception and ExecutionPolicy is one of the three standard policies, std::terminate is called. For any other ExecutionPolicy, the behavior is implementation-defined.

I interpret that this means that I cannot, out-of-the-box, throw from the for_each passed function and expect to catch the excetion or some information related to it.

The reason I was expecting to use exceptions was so that I could partially undo (revert) the changes made in the for_each call. (Maybe there is a better algorithm for that).

However, just by chance I found a historical version of for_each which is documented to have a different, more interesting behavior:

http://man.hubwiz.com/docset/C.docset/Contents/Resources/Documents/output/en/cpp/algorithm/for_each.html

  • if policy is std::parallel_vector_execution_policy, std::terminate is called
  • if policy is std::sequential_execution_policy or std::parallel_execution_policy, the algorithm exits with an std::exception_list containing all uncaught exceptions. If there was only one uncaught exception, the algorithm may rethrow it without wrapping in std::exception_list. It is unspecified how much work the algorithm will perform before returning after the first exception was encountered.

Which seems to imply that instead of terminateing, there is the possibility of actually using exceptions.

So, why std::exception_list was eliminated?, was it too controversial, too complicated, too (memory) costly?

Even if I agree with the logic, I really don't have any other option because parallel for_each returns void (instead of the UnaryFunction back, which is also surprising). Therefore, this std::exception_list protocol seem to me in a necessary component to undo a failed-to-complete for_each instruction.

Is it reasonable to expect that some new custom policy, e.g. par_with_failed_list will appear somewhere allowing for undoing.


More context: This pattern of undoing a failed loop is used in construction of containers. I want to implement a custom (parallel/sequencial) uninitialized_value_construct_n which "undo's" (destroy) the initilized objects when (any of the unsequenced) constructions fail.


EDIT1: On a second though, it might be possible to pass a captured variable in the lambda to a function parameter. This variable could be a shared concurrent data that can store the exceptions as they happen (as a exception_list). I wonder if this has been done already.


EDIT2: I found an implementation of exception_list in HPX,
https://github.com/STEllAR-GROUP/hpx/blob/master/hpx/exception_list.hpp
https://github.com/STEllAR-GROUP/hpx/blob/master/src/exception_list.cpp

like image 503
alfC Avatar asked Mar 13 '19 07:03

alfC


1 Answers

std::exception_list added a lot of complexity to the specification and implementation of parallel algorithms without much corresponding gain.

As a user, you can handle this case in the functor:

struct exception_info{
    ElementType* element;
    std::exception_ptr exception;
};
std::vector<exception_info> exceptions;
std::mutex exceptions_mutex;

std::vector<ElementType> range=...;

std::for_each(std::execution::par,range.begin(),range.end(),[&](ElementType& element){
    try{ do_stuff(element); }
    catch(...){
        std::lock_guard guard(exceptions_mutex);
        exceptions.push_back({&element,std::current_exception()});
    }});

The exceptions list will now contain a list of pointers to the elements where an exception was thrown and the thrown exceptions.

like image 81
Anthony Williams Avatar answered Oct 24 '22 03:10

Anthony Williams