Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rationale for std::move_if_noexcept still moving throwing move-only types?

move_if_noexcept will:

  • return an rvalue -- facilitating a move -- if the move constructor is noexcept or if there is no copy constructor (move-only type)
  • return an lvalue -- forcing a copy -- otherwise

I found this rather surprising, as a move-only type that has a throwing move-ctor will still have this move-ctor invoked by code that uses move_if_noexcept.

Has there been given a thorough rationale for this? (Maybe directly or between the lines of N2983?)

Wouldn't code be better off not compiling rather than still having to face the unrecoverable move scenario? The vector example given in N2983 is nice:

void reserve(size_type n)
{
  ... ...
                 new ((void*)(new_begin + i)) value_type( std::move_if_noexcept( (*this)[i]) ) );
        }
        catch(...)
        {
            while (i > 0)                 // clean up new elements
               (new_begin + --i)->~value_type();

            this->deallocate( new_begin );    // release storage
            throw;
        }
*!*     // -------- irreversible mutation starts here -----------
        this->deallocate( this->begin_ );
        this->begin_ = new_begin;
        ... ...

The comment given in the marked line is actually wrong - for move-only types that can throw on move construction, the - possibly failing - irreversible mutation actually already starts when we move the old elements into their new positions.

Looking at it briefly, I'd say that a throwing move-only type couldn't be put into a vector otherwise, but maybe it shouldn't?

like image 349
Martin Ba Avatar asked Nov 05 '13 22:11

Martin Ba


1 Answers

Looking at it briefly, I'd say that a throwing move-only type couldn't be put into a vector otherwise, but maybe it shouldn't?

I believe you've nicely summed up the choices the committee had for containers of move-only-noexcept(false)-types.

  1. Allow them but with basic exception safety instead of strong for some operations.
  2. Disallow them at compile time.

A. The committee absolutely felt that they could not silently change existing C++03 code from having the strong exception safety to having basic exception safety.

B. For those functions that have strong exception safety, the committee much preferred to have those members continue to have strong exception safety, even for code that could not possibly be written yet (e.g. for functions manipulating move-only types).

The committee realized it could accomplish both of the objectives above, except for the case in B) where the move-only type might throw during move construction. These cases are limited to a few member functions of vector IIRC: push_back, reserve. Note that other members of vector already offer only basic exception safety (even in C++98/03), e.g.: assignment, insert (unless inserting at the end), erase.

With all this in mind, it was the committee's decision that should the client create a vector of a move-only-noexcept(false)-type, it would be more useful to the client to relax the strong exception safety to basic (as it already is for other vector members), rather than to refuse to compile.

This would only be new code that the client writes for C++11, not legacy code, since move-only types do not exist prior to C++11. And no doubt the educators of C++11 should be encouraging their students to write noexcept(true) move members. However code with the basic exception safety guarantee is not so dangerous, nor unusual, such that it should be forbidden. After all, the std::lib is already chock full of code carrying only the basic exception safety guarantee.

like image 184
Howard Hinnant Avatar answered Sep 23 '22 16:09

Howard Hinnant