Exceptionally Moving!

update: we have posted a slideset related to this article

update #2: If N2983 is accepted (as I hope and believe it will be), some of the information in this article will be obsolete. Please read it if you want a complete picture of the issues discussed here.

Welcome to the fifth in our series of articles about efficient value types in C++. In our last episode, we left our intrepid readers vigilantly searching for the optimal implementation of move assignment. Today we’ll find our way through a tough neighborhood of Move City where perfectly ordinary types can clash in suprising ways.

In an earlier posting, we saw that by offering permission to move, one piece of code can work on both movable and non-movable types, taking advantage of move optimizations if and when they are available. This “move if you can; copy if you must” approach is great for optimizing code while keeping it compatible with legacy types that haven’t yet got move constructors. However, it puts a new burden on operations offering the strong guarantee.1

Strong Guarantee, Strong Requirements

Achieving the strong guarantee requires partitioning the steps of an operation into two categories:

The strong guarantee depends on a partitioning of operations

The strong guarantee depends on a partitioning of operations

  1. Operations that may throw, but don’t do anything irreversible
  2. Operations that may be irreversible, but don’t throw.

As long as we can break everything into those two categories, and everything in category 1 happens before everything in category 2, we’re home free. A typical example occurs in C++03 when vector::reserve() has to allocate new element storage:

void reserve(size_type n)
{
    if (n > this->capacity())
    {
        pointer new_begin = this->allocate( n );
        size_type s = this->size(), i = 0;
        try
        {
            // copy to new storage: can throw; doesn't modify *this
            for (;i < s; ++i)
                 new ((void*)(new_begin + i)) value_type( (*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;
        this->end_ = new_begin + s;
        this->cap_ = new_begin + n;
    }
}

In a move-enabled implementation, we’d add an explicit call to std::move in the try block, changing the loop to:

for (;i < s; ++i)
     new ((void*)(new_begin + i)) value_type( std::move( (*this)[i] ) );

The interesting thing about this change is that, when value_type is move-enabled, the loop does modify *this (an explicit request to move from an lvalue is a logically-mutating operation).

A move that might throw can't be rolled back without potentially throwing again

A move that might throw can't be rolled back without potentially throwing again

Now, if the move operation can throw an exception, the loop makes an irreversible change, because rolling back a partially-completed loop would require more move operations. Therefore, to preserve the strong guarantee when value_type is move-enabled, its move constructor must not throw.

Upshot

The draft C++0x standard essentially outlaws throwing move constructors,2 and we suggest that you follow that rule. However, the rule that move constructors must not throw isn’t always easy to follow. Take, for example, std::pair<std::string,UserType>, where UserType is some class with a throwing copy constructor. In C++03, this type is well-behaved and can be placed in a std::vector. In C++0x, however, std::string acquires a move constructor, and so does std::pair:

template <class FirstType, class SecondType>
pair<FirstType,SecondType>::pair(pair&& x)
  : first(std::move(x.first))
  , second(std::move(x.second))
{}

…and here we have a problem. Second is UserType, a type with no move constructor, which means the construction of second above is a (throwing) copy, rather than a move. So pair<std::string,UserType> has acquired a throwing move constructor, and can no longer be used in a std::vector without breaking the strong guarantee.

Today, that means we’ll need something like the following code to make pair work.

1
2
3
4
5
6
7
8
9
10
11
12
template <class T1, class T2>
pair(pair<T1,T2>&& rhs
  , typename enable_if<                 // Undocumented optional
        mpl::and_<                      // argument, not part of the
            boost::has_nothrow_move<T1> // public interface of pair.
          , boost::has_nothrow_move<T2>
        >
     >::type* = 0
)
  : first(std::move(rhs.first)),
    second(std::move(rhs.second))
{};

The use of enable_if causes this constructor to “disappear” unless has_nothrow_move yields true for both T1 and T2.

As far as we know, there’s no way to detect the presence of a move constructor, much less a nonthrowing one, so until we get some new language features, boost::has_nothrow_move is going to be one of those imperfect traits that reports false for user-defined types unless you specialize it. So, whenever you write a move constructor, it should be accompanied by a specialization of this trait. For example, if we added move constructors to std::vector and std::pair, we’d also add:

namespace boost
{
    // All vectors have a (nothrow) move constructor
    template <class T, class A>
    struct has_nothrow_move<std::vector<T,A> > : true_type {};
 
    // A pair has a (nothrow) move constructor iff both its
    // members do as well.
    template <class First, class Second>
    struct has_nothrow_move<std::pair<First,Second> >
      : mpl::and_<
           boost::has_nothrow_move<First>
         , boost::has_nothrow_move<Second>
        > {};
}

We admit this is all pretty awful. The C++ committee is still discussing the details of how to solve this problem, but a few things are generally agreed:

  • We can’t break existing code by silently dropping the strong guarantee.
  • The problem can be made much smaller by generating default move constructors—as suggested by Bjarne Stroustrup in N2904—when appropriate. That will fix the problem for pair and a whole host of types like it, not to mention speeding up some code “for free” by adding generated move optimization.
  • There are still types for which we’ll need to deal with this issue “manually.”

The “Problem Types”

Types in the problem category typically have some sub-objects we’d like to move—provided that can be done safely—and others with which we need to “do something else.” One example is std::vector, which contains an allocator whose copy constructor can throw exceptions:

1
2
3
4
5
6
7
8
9
vector(vector&& rhs)
  : _alloc( std::move(rhs._alloc) )
  , _begin( rhs._begin )
  , _end( rhs._end )
  , _cap( rhs._cap )
{
    // "something else"
    rhs._begin = rhs._end = rhs._cap = 0;
}

A straight member-wise move, like the one generated by default under N2904, wouldn’t have the right semantics here. In particular, it wouldn’t zero-out the _begin, _end, and _cap members of rhs. However, if rhs._alloc doesn’t have a nonthrowing move constructor, it will be copied in line 2. If that copy can throw, vector has acquired a throwing move constructor.3

The challenge for the language designers is to avoid asking the user to supply the same information twice. Having to specify the types of the members that will be moved in the move constructor’s signature (lines 5 and 6 in the pair move constructor above) and then actually move those members in the constructor’s member initialization list (lines 10 and 11). One possibility under discussion is to use the new attribute syntax, so vector’s move constructor might be written this way:

vector(vector&& rhs) [[moves(_alloc)]]
  : _begin( rhs._begin )
  , _end( rhs._end )
  , _cap( rhs._cap )
{
    rhs._begin = rhs._end = rhs._cap = 0;
}

That constructor would be “SFINAE‘d out” unless _alloc itself has a nonthrowing move constructor, and that member would be implicitly initialized by moving from the corresponding member of rhs.

Unfortunately, there has been some disagreement over the proper role of attributes in C++0x, so we still don’t know what syntax the committee will settle on, but at least we think we understand the problem in principle, and how to address it.

Ramblin’ On

Well, thanks for reading; that’s it for today. Next in this series we’ll discuss perfect forwarding, and we haven’t forgotten that we owe you a survey of C++03 move emulations.


  1. The strong guarantee says that if an exception is thrown, there are no effects 

  2. More precisely, it says that if a move constructor throws during a standard library operation, the behavior is undefined. 

  3. Fixing the exeception-safety of vector’s move constructor becomes even more involved than fixing pair’s, which is already templatized. Since enable_if requires a templated function in order to take effect, we end up with a signature like this one:

    template <class T2, class A2>
    vector(
      vector<T2,A2>&& rhs,
      boost::enable_if<
          mpl::and_<
              boost::is_same<T,T2>
            , boost::is_same<A,A2>
            , has_nothrow_move<A>
          >
      >* = 0
    )

Posted Monday, October 5th, 2009 under Value Semantics.

29 Responses to “Exceptionally Moving!”

  1. Sebastian says:

    About move_if_noexcept of N2983: It is proposed to be

    template <class T>
     typename conditional<
         !nothrow_move_constructible<T>::value
         && has_copy_constructor<T>::value,
         T const&,
         T&&
     >::type
     move_if_noexcept(T& x)
     {
         return std::move(x);
     }
     

    Let me rewrite the return type without double negative so it’s less confusing:

     typename conditional<
         nothrow_move_constructible<T>::value
         || !has_copy_constructor<T>::value,
         T&&
         T const&,
     >::type
     

    So, appearently move_if_noexcept also returns an rvalue reference for move-only types regardless of whether the move constructor throws or not. If the function’s name is any indication of a contract this looks like a breach of contract to me.

    In addition, there’s a chance that some user wants to use move_if_noexcept for move assignments as well, not just move constructions. But the conditional just checks for a throwing move construction. Now, I realize that for most move-enabled types nothrow_move_constructible<T> will be equal to nothrow_move_assignable<T> but it is also possibe to write a class where only one of these operations throws and the other one doesn’t. Consider a type with a non-throwing move ctor but a throwing move assignment. This definition of move_if_noexcept will allow a throwing move assignment in this case.

    What about this one?

    template <class T>
     typename conditional<
         nothrow_move_constructible<T>::value
         && nothrow_move_assignable<T>::value,
         T&&
         T const&,
     >::type
     move_if_noexcept(T& x)
     {
         return std::move(x);
     }
     

    This implementation honors its contract. It only returns an rvalue reference if both operations (move construction and move assignment) don’t throw. In rare cases it’ll decrease performance due to being a little more conservative. But I think that’s acceptable. It’ll also lead to some code being rejected. There just isn’t anything you can do about move-only types with a throwing move, is there?

       (Quote)
    • Sebastian says:

      Ok, I think I found a wart of my proposal. There’s a category of move-only types that are not assignable. In this case nothrow_move_assignable<T> will be false (because there is no assignment) but that alone must not prevent move_if_noexcept from returning an rvalue reference. It’ll get better if we say what we mean without negations:

      template <class T>
       typename conditional<
              potentially_throwing_construction<T,T&&>::value
           || potentially_throwing_assignment<T,T&&>::value
           T const&,
           T&&,
       >::type
       move_if_noexcept(T& x)
       {
           return std::move(x);
       }
       

      I think that’s exactly what move_if_noexcept should do. But I’m not sure if it’s possible to get something equivalent with the traits that are defined in N3000.

         (Quote)
      • Sebastian says:

        OK, now I see the advantage of move_if_noexcept returning T&& for move-only types. It’s quite possible that overly conservative implementations of nothrow_xxx will evaluate to false even for types like unique_ptr with non-throwing move operations. But if we return T const& in those cases the code won’t compile on some C++ implementations. This is bad. Better: Overly conservative nothrow_xxx implementations should only decrease performance (if anything) but not stop code from compiling. Returning T&& in those cases is exactly what the proposed move_if_noexcept from N2983 does and I agree that it’s the way to go. In this light, the function’s name is short for “move if no exception can be thrown OR we don’t have a choice w.r.t. move-only types”.

        But what about move assignment?

        After some further pondering I came to the following conclusion (using proposed type traits from N2983):

         
          template<typename T>
          struct has_potentially_throwing_move_ctor
          : std::integral_constant<bool,(
            has_move_constructor<T>::value &&
            !nothrow_move_constructible<T>::value
          )> {};
         
          template<typename T>
          struct has_potentially_throwing_move_assign
          : std::integral_constant<bool,(
            has_move_assign<T>::value &&
            !nothrow_move_assignable<T>::value
          )> {};
         
           template <class T>
          typename conditional<
               (   has_potentially_throwing_move_ctor<T>
                && has_copy_constructor<T> )
            || (   has_potentially_throwing_move_assign<T>
                && has_copy_assignment<T> ) ,
            T const&,
            T&&
          >::type move_if_noexcept(T& x) { return std::move(x); }
         

        The function doesn’t exactly behave like its name suggests — it does return T&& in some cases even if a move operation potentially throws — so maybe there’s a better name for it. But currently, its behaviour looks like a good compromise to me.

        I’d love some feedback.

           (Quote)
  2. Thomas Petit says:

    “Next in this series we’ll discuss perfect forwarding”

    Any news ? I’m really eager to see that one. :)

    It’s odd because in all article on rvalue references I saw (including yours so far :) ), a big part is always assign to move semantic and very little on perfect forwarding. And yet, I have the feeling that perfect forwarding will be as much important in Ox as move semantic.

       (Quote)
  3. akrzemi1 says:

    Hi, In the first article of this series, you promised to provide the definition of Value Semantics. I would be very interested if there was any strict definition of the concept. I thought that I ‘feel’ what it is but when I once thought of the definition it all becomes vague. I can think of at least three:

    1. Using copy/move constructor, copy/move assignment and operator==/!= properly.
    2. Objects (of user defined types) represent values that can be copied, and compared.
    3. We pass arguments to functions and return from functions by value.

    Therefore, a number of questions arise regarding the name ‘Value Semantics’:

    1. Is it applicable only to languages of C family (C++, D, Java, C#)?
    2. Is it correct that objects in Java support value semantics, because we have: copy constructor and methods clone and equals, which provide copying and comparison?

    I was wondering if I could request an article on the definition of Value Semantics.

    Regards, &rzej

       (Quote)
    • akrzemi1: Hi, In the first article of this series, you promised to provide the definition of Value Semantics. I would be very interested if there was any strict definition of the concept.

      I’m sure there are several floating around out there. I’m less sure that I would agree with any of them :-)

      I thought that I ‘feel’ what it is but when I once thought of the definition it all becomes vague. I can think of at least three:
      1. Using copy/move constructor, copy/move assignment and operator==/!= properly.
      2. Objects (of user defined types) represent values that can be copied, and compared.
      3. We pass arguments to functions and return from functions by value.

      Those are all in the right ballpark as far as I’m concerned.

      Therefore, a number of questions arise regarding the name ‘Value Semantics’:
      1. Is it applicable only to languages of C family (C++, D, Java, C#)?
      2. Is it correct that objects in Java support value semantics, because we have: copy constructor and methods clone and equals, which provide copying and comparison?
      I was wondering if I could request an article on the definition of Value Semantics.

      You can certainly request, and we do intend to fulfill all of our promises, eventually ;-) Unfortunately, I haven’t thought all the issues through quite enough yet to write that article today. However, I can address your two questions above:

      1. Actually, of the above only C++ really supports what I mean by value semantics. Here’s a crucial test: can you write a mutable Matrix type that has the usual copy, assignment, etc. semantics? Of crucial importance is that in this example,

        1
        2
        
        Matrix x = y;
        y[0][0] = 3.14;

        the value of x is not modified by line 2.

      2. For me, syntax matters. Copying an object via y = x.clone() and especially comparing objects with x.equals(y) don’t count :-)

         (Quote)
  4. James Hopkin says:

    Regarding the analogy between destructors and move constructors, whereas destructors that throw can violate even the basic exception guarantee, the crime of a move constructor that throws is to make an otherwise strongly exception-safe function only provide the weak guarantee. For example, a vector<T>::reserve which std::moves each element on reallocation: it would be strongly exception safe (as required by the Standard) only if C++0x containers were to require no-throw move operations.

    If C++0x is to allow throwing move operators, I think a strong_move function might be useful. It would be almost identical to std::move; the sole difference is that it returns an lvalue reference if its type parameter has a move constructor or move assignment operator that throws (i.e. has not been declared throw(false) to use Rani’s syntax). Use std::move in a function only advertising the basic exception guarantee; if the function you’re writing requires the strong guarantee use strong_move. (std::move would be sufficient on local variables; strong_move is intended for objects that outlive the operation, such as vector elements in the case of vector<T>::reserve).

    I agree with most of N2904. To incorporate throwing move operations, in the Basic Idea section:

    1. CM+CM => CM
    2. CM+C => C
    3. CM+M => M
    4. C+C => C
    5. M+M => M
    6. M+C => nothing

    M should be taken to mean ‘has non-throwing move constructor’. This way all generated move operations can be no-throw.

       (Quote)
    • Rani Sharoni says:

      Move only types (e.g. unique_ptr) requires some consideration.

      I see two options:

      1) Disallow throwing move ctor for such (compile-time though I’m fine with UB)

      2) Relax relevant operations to provide only basic guarantee for such – no breaking change since the move-only beast is new

      From usability POV, “better something than nothing”, I prefer #2 since (unbounded) many clients will anyway prefer speed in context where they don’t care about strong guarantee. Clients that cares about the guarantee will still “enjoy” UB on their own violation (#1 or #2 is same for them). Also for the second, the implementation is easy (AFAICT) – move if no copy (can be encapsulated in utility like right_move()).

      OTOH, it might be harder to formulate “conditional strong guarantee” and throwing move only types are probably rare so maybe #1 is fine.

      Rani

         (Quote)
      • Rani Sharoni: it might be harder to formulate “conditional strong guarantee” and throwing move only types are probably rare so maybe #1 is fine.

        I don’t see why we’d have any problem formulating the conditional strong guarantee.

        Also, to be fair, types with throwing move ctors are rare, irrespective of whether they can be copied.

           (Quote)
        • Rani Sharoni says:

          In LWG DR #985 I actually asked to relax the strong guarantee in cases that throwing move doesn’t allow it. In the DR, standard containers types are required not to have throwing move ctor to avoid regression (e.g. vector<map>::reserve).

          The relaxation only require text about “conditional strong guarantee” without implementation change (e.g. std::move in vector::reserve is fine as is) but some thought that the relaxation is too risky/confusing and therefore I thought about the “conditional non-throwing move optimization” to keep the strong guarantee but still allow throwing move.

          Throwing move ctors are rare as throwing default ctors (e.g. Dinkumware’s node based containers) but it seems that move only types are rare by themselves so throwing move ctor for such should be very rare. I personally prefer the “better than nothing” relaxation for the favor of move-only in order to avoid any “rarity analysts”.

          Rani

             (Quote)
  5. Rani Sharoni says:

    Hi Dave,

    Your initiative is highly appreciated and I was especially waiting for this installment. We already had some private discussions about move’s exception safety and I still hope to figure better conclusion.

    Our basic disagreement is on whether throwing move should be allowed. In LWG DR #985 (http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#985) I made some points on why to allow throwing move and I still think that it’s worth considering.

    Stroustrup and you compared move constructor to destructor hence no-throw is required. I’ve pointed that move constructor is like default constructor since it typically leaves the source object in an “empty state” so throwing default ctor yields throwing move constructor. Therefore, IMO, disallowing throwing move is as restrictive as disallowing throwing default ctor (and current MS-VC STL has some).

    Another example, as shown from std::pair, is “partial” move in which move constructor of class with members of legacy types will move what it can (optimal move). Note that, as you’ve shown, move assignment is usually like swap though partial move assignment is also possible.

    In general, even throwing move is typically faster than full copy so it’s better not to penalties the non-exceptional code path by preferring throwing move over full copy.

    So from usability POV it’s better to allow throwing move (relaxation). This is somewhat similar to allowing throwing copy constructor (something that was almost rejected from C++98).

    Now let’s consider the (STL) implementation restrictions from allowing throwing move (e.g. inability to optimize). In many use cases, move ctor is used in throwing contexts – when constructing the object before the move. Therefore most operations with strong guarantee are not affected by throwing move (e.g. insert to node based container). The problem is with aggressive move optimizations such as vector’s range move where move from l-value is involved and I think that such cases are not important enough to disallow better usability.

    IMO, the user should opt-in for such aggressive optimization using non-throwing move (“opt-in via no-throw”) and the implementation should condition aggressive optimization based on the existence of throwing move (i.e. quality of implementation is allowed).

    Also for my suggestion, adding language support for making non-throwing functions detectable is essential. The STL implementation will allow throwing move (e.g. copy if throwing-move) and the optimizer will be allowed to add implicit non-throwing when possible.

    It’s also useful, for advanced implementations, to allow (compile time) conditional no-throw operations. For example, std::pair’s move constructor will be non-throwing if both types have non-throwing move. This can be done using syntax like throw(const Boolean expression) – e.g. f::f(T&&) throw(has_throwing_move::value).

    IMHO, such “modest” language support will allow safe and optimized code.

    Rani

       (Quote)
    • Rani Sharoni Our basic disagreement is on whether throwing move should be allowed. In LWG DR #985 (http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#985) I made some points on why to allow throwing move and I still think that it’s worth considering. Stroustrup and you compared move constructor to destructor hence no-throw is required.

      I don’t think I ever made that comparison, did I?

      I’ve pointed that move constructor is like default constructor since it typically leaves the source object in an “empty state” so throwing default ctor yields throwing move constructor. Therefore, IMO, disallowing throwing move is as restrictive as disallowing throwing default ctor (and current MS-VC STL has some).

      Yes.

      IMO, the user should opt-in for such aggressive optimization using non-throwing move (”opt-in via no-throw”) and the implementation should condition aggressive optimization based on the existence of throwing move (i.e. quality of implementation is allowed).

      You are suggesting that operations in this category should only use move when they can prove that move is non-throwing?

      Also for my suggestion, adding language support for making non-throwing functions detectable is essential.

      Yup. At the very least, the library needs the ability to do that. And if the library can do it, why not the user?

      The STL implementation will allow throwing move (e.g. copy if throwing-move) and the optimizer will be allowed to add implicit non-throwing when possible.

      Sorry, I don’t follow this part. What’s “implicit non-throwing?”

      It’s also useful, for advanced implementations, to allow (compile time) conditional no-throw operations. For example, std::pair’s move constructor will be non-throwing if both types have non-throwing move. This can be done using syntax like throw(const Boolean expression) – e.g.f::f(T&&) throw(has_throwing_move::value). IMHO, such “modest” language support will allow safe and optimized code. Rani

      Well, that’d sure be nice to have. Unfortunately, adding a feature like statically-checked exception specifications (essentially what you’re proposing, (and what Doug and I proposed with “noexcept“) at this late stage of standardization would face significant opposition. I think we decided that Bjarne’s suggestion to give move construction first-class status and simply declare it to be nonthrowing was simpler. However, now that I look at it, I am no longer convinced. When I add up all the complexities, I must admit that I find your arguments compelling.

      To me, it even looks like we could allow std::pair to acquire a throwing move ctor without breaking any user code, because the only code whose semantics depend on whether a move throws must use std::move. I.e., no such code exists yet.

         (Quote)
      • Rani Sharoni says:

        (I wasn’t able to figure out how to indent the post in your nice style)

        Rani >> Stroustrup and you compared move constructor to destructor hence no-throw is required.

        Dave > I don’t think I ever made that comparison, did I?

        I might be wrong but Stroustrup was explicit in N2904 “I suggest that it should be an exact parallel to the rule that destructors should not throw.”

        Dave > You are suggesting that operations in this category should only use move when they can prove that move is non-throwing?

        Yes. code for which throwing move ctor is not allowed (non preserving optimization) is rare and in some sense is of different category. IMO, the language should allow various QOI in respect to move optimizations such as fallback to “default constructor and non-throwing move assignment” if possible (since move-assignment is similar to swap so it’s less likely to throw hence it make sense to optimize accordingly). detectable no-throw will allow such.

        Rani >> making non-throwing functions detectable is essential.

        Dave > And if the library can do it, why not the user?

        The advanced user can write conditional optimizations but it’s still legitimate to allow throwing move. It seems non realistic to have such restriction on ordinary users when current STL implementations have throwing default constructor hence throwing move ctor (e.g. std::map in MS VC).

        Rani >> optimizer will be allowed to add implicit non-throwing when possible.

        Dave > What’s “implicit non-throwing?”

        In case that the compiler proves that func is not throwing then “is-throwing-operation(func)” will be false even if ‘func’ was not explicitly declared with throw(). Compilers are already doing such no-throw optimizations today (e.g. for generic functions like std::copy when used with non throwing operations). move ctors are usually simple enough for the compiler to deduce that they are not throwing.

        Such implicit non-throwing will allow many aggressive optimizations without the burden of verbose no-throw annotations.

        Dave > Unfortunately, adding a feature like statically-checked exception specifications at this late stage of standardization would face significant opposition

        I know that there is slim chance for that but maybe such feature (both detectable throw and conditional throw) can be optional since anyway it should not change the semantics of the program (at worst there are no aggressive optimizations but no broken code). throw(true/false) should be independent from existing throw-specifications and no run-time impact (i.e. throw from throw(false) is UB).

        Rani

           (Quote)
        • Rani Sharoni: (I wasn’t able to figure out how to indent the post in your nice style)

          Hit “reply,” then scroll up and click “(Quote)” next to the date of the post you’re replying to. And read the “Posting” page linked at the top of the page. I’ll try to make this easier someday :-)

          Stroustrup and you compared move constructor to destructor hence no-throw is required.
          I don’t think I ever made that comparison, did I?
          I might be wrong but Stroustrup was explicit in N2904 “I suggest that it should be an exact parallel to the rule that destructors should not throw.”

          Yes, but as you may notice, I had no hand in N2904 :-)

          making non-throwing functions detectable is essential.
          And if the library can do it, why not the user?
          The advanced user can write conditional optimizations but it’s still legitimate to allow throwing move.

          I wasn’t arguing otherwise.

          Unfortunately, adding a feature like statically-checked exception specifications at this late stage of standardization would face significant opposition
          I know that there is slim chance for that but maybe such feature (both detectable throw and conditional throw) can be optional since anyway it should not change the semantics of the program (at worst there are no aggressive optimizations but no broken code). throw(true/false) should be independent from existing throw-specifications and no run-time impact (i.e. throw from throw(false) is UB).

          Yeah, but if we don’t also mark dtors “noexecpt” (or throw(false) if you prefer—I like that way of avoiding a new keyword, BTW) by default, it’ll be a huge missed opportunity, and that raises the whole anti-nannying argument. Still, I very much like the direction this is going in, and even though it’s too late for the pre-Santa Cruz mailing, I would be very pleased—in my “copious spare time”—to write a paper on it with you and try to get it considered.

             (Quote)
          • Rani Sharoni says:

            Some of my posts pass the ‘Preview’ but submit is not working.

            I tried hard to mimic your markdown style but no success yet.

            Dave: Yeah, but if we don’t also mark dtors “noexecpt” (or throw(false) if you prefer— I like that way of avoiding a new keyword, BTW) by default, it’ll be a huge missed opportunity, and that raises the whole anti-nannying argument. /Dave

            IMO, destructors should be implicitly no-throw since it’s always a bug to throw from such. The few exceptions in which dtor throws (e.g. the “error code or throw” type) can use throw(…) to overwrite the default (though I never personally used type with throwing dtor).

            FWIW, in our VC env, the compiler assumes that dtors are not throwing and no-throw is used only for optimizations (i.e. UB on violation). Therefore maybe it’s not “huge missed opportunity” since it’s fine to “mitigate” such standard deficiencies.

            Dave: I would be very pleased—in my “copious spare time”—to write a paper on it with you and try to get it considered. /Dave

            Same here. notify me how you want to proceed with a proposal.

            Thanks, Rani

               (Quote)
            • James Hopkin says:
              Rani Sharoni: IMO, destructors should be implicitly no-throw since it’s always a bug to throw from such. The few exceptions in which dtor throws (e.g. the “error code or throw” type) can use throw(…) to overwrite the default (though I never personally used type with throwing dtor).

              Do you think move operations should default to throw(false)? On the one hand, it would be unfortunate if user-written move operations without throw(false) are ignored by STL (at least by operations with the strong guarantee). On the other, it’s quite easy to accidentally violate the throw(false) from within a move constructor, for example if the class contains a resource holding type without move operations.

                 (Quote)
              • Rani Sharoni says:

                No. default should be might throw but the compiler is free to suggest otherwise based on inlining since most move operations should trivial enough for the optimizer that is anyway doing no-throw analysis (e.g. has_non_throwing_move can return true even the user didn’t explicitly said so).

                AFAICT, most are using exception specification just to override the default for no-throwing and expecting them to do so in reverse (override no-throw) is not realistic.

                Note that not all operations with strong guarantee should be pessimized since the problem is mainly related to “lvalues range move” as suggested for std::vector. Strong operations like std::list::insert are not affected since existing elements are not relocated and the new element will be thrown on exception (I have specified the affected operations in LWG DR #985).

                Rani

                   (Quote)
    • Peter Dimov says:

      “Therefore, IMO, disallowing throwing move is as restrictive as disallowing throwing default ctor (and current MS-VC STL has some).”

      I’m not sure I agree. The alternative to a throwing default constructor is a non-throwing default constructor. The alternative to a throwing move constructor is a throwing copy constructor. Not the same thing at all.

         (Quote)
      • So if you need/want to write a class whose invariant includes a resource-less empty state (as required for a non-throwing default constructor), you can still do that. You simply don’t get to have a move ctor in that case. Makes sense.

        I’m not sure I like that trade-off, but I think Peter’s argument is sound.

           (Quote)
      • Rani Sharoni says:

        Such alternative is penalizing the use case for which move was intended “a = func()” though for such contexts it doesn’t matter if move is throwing and even on might throw it will be faster than full copy in the normal code path (e.g. with std::map of current move supporting VC).

        Beside of optimization opportunities, disallowing by STL is in practice disallowing throwing move altogether and I find it too restrictive (per my above points).

        Rani

           (Quote)
  6. Sebastian says:

    Great article!

    I think N2953 is worth pointing out as well. Though, I think that one of the requirements for generating implicit move ctors (“all members have a move ctor”) could be changed to “all members have a no-throw constructor that takes an rvalue by reference” which won’t rule out non-throwing copy ctors. — Apropos, what happened to “noexcept”?

    BTW: I think the second for-loop in your example should read “for(;i– > 0;)” instead of “for(;i > 0; –i)”.

       (Quote)
    • Sebastian: Great article! I think N2953 is worth pointing out as well.

      Good point; I hadn’t seen it. Thanks for the pointer!

      Though, I think that one of the requirements for generating implicit move ctors (”all members have a move ctor”) could be changed to “all members have a no-throw constructor that takes an rvalue by reference” which won’t rule out non-throwing copy ctors. — Apropos, what happened to “noexcept”?

      Well, some people began to argue that the associated compile-time checks were going to make users feel nannied and harassed, and at the same time argued that noexcept treated a symptom of the problem rather than the root cause (see N2904)—although I have a hard time seeing how generating defaults addresses a root cause when the problem still exists for types like std::vector<T,A>. In any case, Doug and I thought the idea of generating default copy and move was a very good one, because it makes the problem cases much rarer, once you are willing to accept the premise that a move constructor just shouldn’t throw. So in Frankfurt it looked to us like adding a mechanism to SFINAE-away the offending move ctors was a much simpler change than what we had been proposing. However, from my follow-up to Rani’s comment I think you can see I’m no longer convinced.

      As for the language change you propose for N2953, you should take that up with the authors of the paper :-)

         (Quote)
  7. M says:

    I cannot seem to find the “lines 10 and 11″ :)

       (Quote)

Leave a Comment (post replies using links below individual comments)

Spam Protection by WP-SpamFree

Subscribe without commenting