Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r50143 - sandbox/committee/rvalue_ref
From: dave_at_[hidden]
Date: 2008-12-05 14:48:09


Author: dave
Date: 2008-12-05 14:48:08 EST (Fri, 05 Dec 2008)
New Revision: 50143
URL: http://svn.boost.org/trac/boost/changeset/50143

Log:
begin collaborative work
Added:
   sandbox/committee/rvalue_ref/
   sandbox/committee/rvalue_ref/n2812_08-0322_soundness.rst (contents, props changed)

Added: sandbox/committee/rvalue_ref/n2812_08-0322_soundness.rst
==============================================================================
--- (empty file)
+++ sandbox/committee/rvalue_ref/n2812_08-0322_soundness.rst 2008-12-05 14:48:08 EST (Fri, 05 Dec 2008)
@@ -0,0 +1,132 @@
+Synopsis
+========
+
+This paper describes a safety problem with rvalue references. The underlying
+issue has been known for some time, but recently-discovered examples have made
+its seriousness much more apparent. We also propose a solution to the problem.
+
+Example
+=======
+
+Consider::
+
+ template <class Cont>
+ void assign(queue<Cont>& dest, Cont const& src); // #1: copy src into dest
+
+ std::vector<int> x;
+ queue<std::vector<int> > q;
+
+ assign(q, x); // copy from lvalue
+ assign(q, std::vector<int>(10)); // copy from rvalue
+
+The Move/Copy Overload Idiom
+============================
+
+In order to optimize the 2nd call to assign, we can add move semantics support
+with the following typical idiomatic use of rvalue references::
+
+ template <class Cont>
+ void assign(queue<Cont>& dest, Cont&& src); // #2: move from src rvalues into dest
+
+Although moving from src changes it, assign is still logically non-mutating on
+its second argument. The optimization that moves from the argument is only
+applied when that argument is an unnamed temporary and thus inaccessible and
+invisible to the rest of the program.
+
+How Move-Only Types Work
+========================
+
+A movable but non-copyable argument type follows the same binding pattern as
+std::vector<int> does: rvalue arguments, which can be safely moved from, select
+overload #2::
+
+ queue<move_only_container> q2;
+ assign(q2, move_only_container());
+
+As before, lvalue arguments select overload #1::
+
+ move_only_container y;
+ assign(q2, y);
+
+However, since the argument type is noncopyable, the body of #1 fails
+compilation when it attempts to make a copy.
+
+The Problem
+===========
+
+The problem is that the lvalue/rvalue overload set doesn't degrade safely. If
+overload #1 is removed from consideration, overload #2 will match both rvalues
+and lvalues, moving silently from all mutable arguments.
+
+When Will That Happen?
+======================
+
+There are a number of possible reasons for such a removal, but simple programmer
+blunders may be the most likely causes. For example, an errant finger might hit
+the delete key when overload #1 is selected.
+
+Some mistakes are not nearly so obvious. For example, suppose we want the
+ability to control allocation when we know the source container is going to be
+copied. We might modify overload #1 as follows::
+
+ // #1 with optional allocator
+ template <class Cont>
+ void assign(queue<Cont>& dest, Cont const& src,
+ typename Cont::allocator_type = src.allocator());
+
+All is well until the user forgets to define a nested allocator_type in his
+container and SFINAE eliminates overload #1, again moving from lvalues.
+
+Adding Concept Constraints
+==========================
+
+To use our assign function in a constrained context, we'll need to add
+concept constraints for the operations performed in the function body::
+
+ template <class Cont>
+ requires CopyAssignable<Cont>
+ void assign(queue<Cont>& dest, Cont const& src); #1
+
+ template <class Cont>
+ requires MoveAssignable<Cont>
+ void assign(queue<Cont>& dest, Cont&& src); #2
+
+Passing an argument that doesn't meet the CopyAssignable constraint causes
+overload #1 to be removed via SFINAE. In other words, *any* move-only argument,
+even an lvalue, will select overload #2.
+
+Why This Happens
+================
+
+There is no precedent in const-correct code for a non-mutating call to become
+mutating when an overload is removed from the set. So why does it happen here?
+
+In C++03 overload sets where only one overload mutates data (see
+set<T>::operator[]), the mutating operation always binds less liberally to
+arguments than the non-mutating operation. Non-const operations never attract
+const arguments. Rvalue references, however, *will* attract lvalues.
+
+Proposed Solution
+=================
+
+Because an rvalue reference in a function signature is used as a signal that we
+can move, lvalues must not be allowed to bind to rvalue references.
+
+Note: this change does not impact perfect forwarding.
+
+ template <class T>
+ void f(T&& x) { ... forward<T>(x) ... }
+
+When an lvalue of type U is passed to f, T is deduced as U&, and since U& && is
+U&, the actual reference being bound is an lvalue reference.
+
+Impact
+======
+
+The existing definition of std::move takes advantage of the current liberal
+binding rule, so we'd need to add an overload to support lvalues. We'd also
+need to decide whether it makes sense to preserve the new functionality
+supporting rvalue streams. If so, all the streaming operators that were changed
+for C++0x to take an rvalue reference first argument would need a second
+overload. If not, we should revert these operators to their original
+definitions.


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk