std::movable_box: Give Users the Ranges Movable Wrapper

Document #: P4346R0 [Latest] [Status]
Date: 2026-09-22
Project: Programming Language C++
Audience: LEWG
Reply-to: Steve Downey
<>

1 Abstract

The exposition-only class template movable-box is the workhorse of the range adaptors. It is how the standard library stores a predicate, a projection, or a value inside a view type and still satisfies the assignability the view concept demands. Every implementation ships one. Every ranges-adjacent library reimplements one. Anyone writing a conforming view outside the standard library must write one.

This paper proposes making the type available to users as std::movable_box, with two constraints on the proposal itself:

  1. The interface is exactly the usage surface of clause [ranges]. The type supports the operations the standard library’s views perform on movable-box today, and no more. It is a tool for implementing range types, not a new general-purpose maybe-type.

  2. The specification is standalone. [range.move.wrap] specifies movable-box as a delta against optional as a wording convenience. That convenience reads as a structural mandate, and it is not one: shipping implementations differ on whether optional is involved at all. The proposed wording is self-contained and does not mention optional.

The semantics of the ranges library are unchanged; the exposition-only name is redefined as an alias for the public type, and the changes to clause [ranges] are borderline editorial.

2 Revision History

2.1 R0

Initial revision.

3 Motivation and Scope

3.1 The problem movable-box solves

Function objects in C++ are frequently not assignable. A lambda with a capture has a deleted copy assignment operator. Types with const or reference members are not assignable. Yet the range adaptors need to store such objects inside views, and views must model movable.

movable-box<T> ([range.move.wrap]) augments a move_constructible object type with assignability: if T is copy constructible but not copyable, copy assignment is synthesized as destroy-then-copy-construct; if T is not movable, move assignment is synthesized as destroy-then-move-construct. A disengaged state exists, but it is an exception artifact (reachable only when a synthesized operation exits via an exception), not a domain state. In this respect the type is the inverse of optional: its default state is engaged (a value-initialized T), and emptiness is never requested, only suffered.

3.2 Everyone already has one

The standard library uses movable-box pervasively. At least the following are specified in terms of it: single_view, repeat_view, filter_view, transform_view, take_while_view, drop_while_view, zip_transform_view, adjacent_transform_view, and chunk_by_view.

Consequently every implementation ships an internal version: libstdc++ has std::ranges::__detail::__box, libc++ has __movable_box, and the Microsoft STL has _Movable_box. The design originates in range-v3’s semiregular_box [range-v3], which user code has been borrowing or reimplementing for the better part of a decade.

Users writing their own views and range adaptors need this component to store their callables and values. Writing them is a task the ranges design intends to support; view_interface and range_adaptor_closure were made public for it. Today their choices are:

  1. Reimplement it, including the subtle valueless-by-exception corner cases and the storage optimization.
  2. Reach into implementation detail namespaces, with the usual consequences.
  3. Depend on range-v3 or another library for one small class template.

None of these is good. The type is nearly fully specified in the working draft already; the standard is simply keeping it to itself.

3.3 Scope: exactly the standard library’s usage, no more

The target user is an implementer of new or existing range types. So the proposal standardizes the observed usage surface of movable-box in clause [ranges]. An audit gives:

That is the entire interface proposed. Everything else optional offers is absent; see the design decisions and the consolidated non-goals in D9.

3.4 History

The wrapper entered the working draft with the ranges design (as exposition-only semiregular-box, [range.semi.wrap] in C++20). [P2325R3] removed the requirement that views be default constructible and renamed the wrapper copyable-box. [P2494R2] relaxed the range adaptors to admit move-only function objects, arriving at today’s movable-box. The specification has been stable since; the type has shipped, in essentially its current form, in three major implementations across two standard cycles.

4 Design Decisions

The guiding principle is: standardize the type that exists, for the use cases the standard library keeps finding for it. Where the existing specification leaves a choice, prefer the resolution already made by [range.move.wrap] [range.move.wrap] and by shipping implementations.

Three principles decide the rest of this section, and are worth stating before the decisions that follow from them.

Fail loudly, not subtly. The type is proposed for the use cases it already matches. Used outside them, it must not compile — not compile and quietly do the wrong thing. An operation is omitted when supplying it would give an answer that is defensible in isolation and wrong in context; the empty state drives most of these, because it is an exception artifact rather than a domain state, and any operation whose semantics turn on it computes over something that should not have been reachable. movable_box<T&> is ill-formed for the same reason (D7). This is the warrant for the interface in D6 and the non-goals in D9. An audit of what the views happen to call is not.

A drop-in for what ships now. A vendor must be able to implement std::movable_box as their existing box and alias the exposition-only name to it, with no view changing layout or behavior. This is why the wording mandates no structure (D5) and why nothing in the interface can make the empty state reachable on demand (D6).

Adoption is a one-time cost. It should be a rename plus a bounded, mechanical set of edits, after which library implementers owe this component nothing further. A delta specification fails this test on its own: it makes every future change to optional into a standing question about movable_box (D5).

The drop-in principle is a condition on the paper; the other two are preferences. If adopting std::movable_box would mandate an ABI break in libstdc++, libc++, or the Microsoft STL, the proposal should be rejected, and I will withdraw it. There is no version of this component worth a break: the whole claim is that the type already exists and users cannot reach it. A vocabulary type is not worth breaking the ABI of every view in the standard library. See Implementation Experience for what has been confirmed and what has not.

4.1 D1: No semantic changes

std::movable_box<T>, restricted to the interface above, has exactly the semantics specified in [range.move.wrap]. The exposition-only movable-box is redefined as an alias for it, so clause [ranges] changes are borderline editorial and no implementation needs to change behavior; vendors may literally rename (or alias) their internal type.

4.2 D2: Name

Proposed: movable_box.

The working draft has called this type semiregular-box, copyable-box, and movable-box as its constraints evolved; the current name accurately describes what the wrapper guarantees (the result always models movable) and matches the name users of the working draft and of cppreference already know.

Alternatives considered:

4.3 D3: Namespace

Proposed: namespace std.

Although the specification currently lives in [ranges], nothing about the type’s semantics is ranges-specific. Placing it in std::ranges would be defensible, given the ranges-shaped scope of this proposal. However, I have a weak preference for std, matching the other small vocabulary components that view authors reach for — in_place_t, and the concepts of [concepts] — and avoiding a second-class name if other library clauses later adopt it. LEWG direction welcome.

4.4 D4: Header

Proposed: <utility>.

With the specification decoupled from optional (D5), <optional> loses its claim. The type’s constituency includes every author of a view type, all of whom include <ranges>; but per D3 the component is not ranges-specific. <utility> is where small, broadly applicable vocabulary components live, and the addition is tiny.

Alternatives: a new <movable_box> header (defensible under the one-component-one-header trend; I am not opposed), <optional> (rejected: implies a structural relationship this paper explicitly disclaims), <ranges> (rejected per D3).

4.5 D5: Standalone specification — optional is not involved

[range.move.wrap] says movable-box “behaves exactly like optional<T>” with enumerated differences. For an exposition-only type whose every use is visible to the specification’s authors, that is an economical technique. However, for a public type it is the wrong shape, twice over:

The proposed wording is therefore self-contained: it defines “contains a value” for movable_box directly, specifies each member’s effects in its own right, and never references optional. This costs perhaps a page of wording and buys structural independence. It also means the disengaged state’s semantics are stated where they belong, as postconditions of the synthesized assignments, rather than inherited from a type whose disengaged state is a first-class feature.

4.6 D6: Interface — the observed usage surface

Proposed: the operations enumerated in Scope: default construction, direct construction from const T&/T&&, in_place construction, the special member functions with the synthesized assignments, operator*, operator->, has_value, and operator bool.

Two things point the same way:

The engagement observers are retained even though views use them only in preconditions: the valueless-by-exception state is real, and a type that can be empty but cannot be asked is a trap. This mirrors the honesty of variant::valueless_by_exception.

The constructors from const T& and T&& are proposed as explicit and non-template. The ranges clauses use only direct-initialization from T itself, so implicit conversion and a templated converting constructor (with its attendant constraint and CTAD questions) are not needed and not proposed.

4.7 D7: Constraints

Unchanged: move_constructible<T> && is_object_v<T>.

The constraint implies two boundaries rather than stating them. Both will be asked about, so:

4.8 D8: Storage and triviality

The recommended practice of [range.move.wrap] p2 is carried over verbatim. Layout is unspecified generally, so it stays a quality-of-implementation matter.

Because the specification is standalone (D5), the triviality guarantees that were previously inherited must now be stated: the destructor is trivial if is_trivially_destructible_v<T> is true, and the copy/move constructors are trivial if T’s corresponding constructors are trivial. These are implementable in the presence of a discriminant (as optional demonstrates) and matter in practice: a trivially copyable movable_box<T> keeps views of trivially copyable payloads trivially copyable, with the ABI and calling-convention consequences that implies. The assignment operators are also specified trivial under the conditions of [optional.assign], so trivially copyable (and trivially assignable) payloads yield trivially copyable boxes — and, transitively, trivially copyable views.

4.9 D9: Non-goals, consolidated

The following are absent. Each is additive, and none is precluded for the future, but the reason each is absent now is that supplying it would answer a question over the empty state, which is an exception artifact and not a domain state. A wrong answer that compiles is worse than no answer.

4.10 D10: constexpr

Fully constexpr, as the exposition-only type already must be for the views to be usable in constant expressions.

4.11 D11: Feature test macro

__cpp_lib_movable_box, in <utility> and <version>.

4.12 D12: Freestanding

The proposed interface has no throwing accessors and no dependency on bad_optional_access; nothing obstructs marking the subclause freestanding. To be confirmed during wording review.

5 Impact on the Standard

A pure library extension: one new class template, one feature test macro, and an editorial redefinition of the exposition-only movable-box as an alias for the new public type. No existing valid program changes meaning. No implementation is required to change the behavior, layout, or ABI of any ranges component.

6 Implementation Experience

Three independent implementations of this type ship today inside libstdc++, libc++, and the Microsoft STL, exercised by the entire range adaptor test suite of each. range-v3’s semiregular_box [range-v3] is the design’s origin and has a decade of field experience.

A reference implementation of the proposed interface exists as beman.movable_box [beman.movable_box], with a test suite written against the wording below rather than against the implementation. That repository is not public yet; the cited URL is where it will be published. The same suite compiles and runs, unmodified, against libstdc++’s exposition-only std::ranges::__detail::__box. Thirty-three of its thirty-seven test cases pass there.

The four failures are not defects. [range.move.wrap] specifies the exposition-only type well, and an exposition-only type owes correct behavior only where the library uses it; every difference below is either unobservable through the views, or is not required by the current exposition-only wording. The question this paper has to answer is not whether they are bugs. It is what adopting the public interface would cost a vendor: whether the differences can be closed without breaking any currently conforming program, and without moving any layout.

The first two were closed experimentally against libstdc++ 15, by replacing the primary template’s using optional<T>::optional; with the four constructors the proposed interface actually has, and adding value_type to both templates. All nine views specified in terms of movable-box still compile and run; __box<Bad> becomes correctly non-default-initializable; and sizeof is unchanged for every box and every view tested, [[no_unique_address]] and the optional base included. No currently conforming program can observe either change: __box is a reserved name, and neither difference is reachable through a view.

So the current libstdc++ result is narrower than “rename plus two mechanical edits.” The interface accidents above can be closed with ABI intact, and the stronger exception specification needs no change, but the public self-assignment guarantee needs either an implementation change or a wording decision. What carries the ABI result is still that the vendor can implement std::movable_box as their box and alias the exposition-only name to it. That is only available because the proposed wording is standalone (D5) and mandates no structure. A specification that made movable_box an optional with patches would put every view’s layout back in play.

This has been confirmed for one implementation of three. libc++’s std::ranges::__movable_box and the Microsoft STL’s _Movable_box have not been tested, and I intend to run the suite against both before asking LEWG to advance the paper.

That is a condition on the proposal rather than an outstanding chore. Per the drop-in principle, a required ABI break in any of the three sinks it. The libstdc++ result is therefore evidence only that adoption can be free; two thirds of the claim is unverified, and the paper should not be advanced on one data point. LEWG should read the result above as a demonstration of method — patch the vendor’s header, compile every view specified in terms of movable-box, compare layouts — and not yet as a conclusion.

7 Proposed Wording

Wording is relative to the current working draft. The specification is self-contained and follows the drafting style of [optional] and [expected] without referencing either.

7.1 [utility.syn]

Add to the header <utility> synopsis:

// [movable.box], class template movable_box
template<class T>
  requires move_constructible<T> && is_object_v<T>
  class movable_box;

7.2 New subclause [movable.box]

Add a new subclause to [utility] (placement editorial):

Movable wrapper [movable.box]

General [movable.box.general]

1 A movable_box<T> either contains a value of type T or contains no value. When a movable_box<T> contains a value, the contained value is allocated within the storage of the movable_box. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate the contained value.

2 A movable_box contains a value after any of its constructors completes without an exception, unless it was copy or move constructed from a movable_box that contains no value. A movable_box can come to contain no value only as specified for its assignment operators, or by being copy or move constructed from a movable_box containing no value. [Note: A movable_box<T> contains no value only as a consequence of an exception thrown during a copy or move assignment; the state then propagates through subsequent copies, moves, and assignments until a value is assigned. — end note]

3 Recommended practice: If copy_constructible<T> is modeled, movable_box<T> should store only a T if either T models copyable, or is_nothrow_move_constructible_v<T> && is_nothrow_copy_constructible_v<T> is true. Otherwise, movable_box<T> should store only a T if either T models movable or is_nothrow_move_constructible_v<T> is true.

namespace std {
  template<class T>
    requires move_constructible<T> && is_object_v<T>
  class movable_box {
  public:
    using value_type = T;

    // [movable.box.ctor], constructors
    constexpr movable_box() noexcept(is_nothrow_default_constructible_v<T>)
      requires default_initializable<T>;
    constexpr explicit movable_box(const T& t) requires copy_constructible<T>;
    constexpr explicit movable_box(T&& t);
    template<class... Args>
      constexpr explicit movable_box(in_place_t, Args&&... args);

    constexpr movable_box(const movable_box& rhs) requires copy_constructible<T>;
    constexpr movable_box(movable_box&& rhs)
      noexcept(is_nothrow_move_constructible_v<T>);

    // [movable.box.dtor], destructor
    constexpr ~movable_box();

    // [movable.box.assign], assignment
    constexpr movable_box& operator=(const movable_box& rhs)
      noexcept(see below) requires copy_constructible<T>;
    constexpr movable_box& operator=(movable_box&& rhs)
      noexcept(see below);

    // [movable.box.obs], observers
    constexpr bool has_value() const noexcept;
    constexpr explicit operator bool() const noexcept;

    constexpr T& operator*() & noexcept;
    constexpr const T& operator*() const & noexcept;
    constexpr T&& operator*() && noexcept;
    constexpr const T&& operator*() const && noexcept;

    constexpr T* operator->() noexcept;
    constexpr const T* operator->() const noexcept;
  };
}

Constructors [movable.box.ctor]

constexpr movable_box() noexcept(is_nothrow_default_constructible_v<T>)
  requires default_initializable<T>;

1 Effects: Equivalent to movable_box(in_place). [Note: The contained value is value-initialized. — end note]

constexpr explicit movable_box(const T& t) requires copy_constructible<T>;

2 Effects: Direct-non-list-initializes the contained value with t.

constexpr explicit movable_box(T&& t);

3 Effects: Direct-non-list-initializes the contained value with std::move(t).

template<class... Args>
  constexpr explicit movable_box(in_place_t, Args&&... args);

4 Constraints: is_constructible_v<T, Args...> is true.

5 Effects: Direct-non-list-initializes the contained value with std::forward<Args>(args)....

constexpr movable_box(const movable_box& rhs) requires copy_constructible<T>;

6 Effects: If rhs contains a value, direct-non-list-initializes the contained value with *rhs; otherwise, *this contains no value.

7 Remarks: This constructor is trivial if is_trivially_copy_constructible_v<T> is true.

constexpr movable_box(movable_box&& rhs)
  noexcept(is_nothrow_move_constructible_v<T>);

8 Effects: If rhs contains a value, direct-non-list-initializes the contained value with std::move(*rhs); otherwise, *this contains no value. rhs.has_value() is unchanged.

9 Remarks: This constructor is trivial if is_trivially_move_constructible_v<T> is true.

Destructor [movable.box.dtor]

constexpr ~movable_box();

1 Effects: Destroys the contained value, if any.

2 Remarks: This destructor is trivial if is_trivially_destructible_v<T> is true.

Assignment [movable.box.assign]

constexpr movable_box& operator=(const movable_box& rhs)
  noexcept(see below) requires copy_constructible<T>;

1 Effects: If this == addressof(rhs) is true, no effects. Otherwise:

  • (1.1) If copyable<T> is modeled: if *this and rhs each contain a value, the contained value of *this is copy-assigned from *rhs; otherwise, if rhs contains a value, a contained value is direct-non-list-initialized with *rhs; otherwise, the contained value of *this, if any, is destroyed.

  • (1.2) Otherwise: the contained value of *this, if any, is destroyed; then, if rhs contains a value, a contained value is direct-non-list-initialized with *rhs. If the initialization exits via an exception, *this contains no value.

2 Returns: *this.

3 Remarks: The exception specification is equivalent to: is_nothrow_copy_constructible_v<T> && (!copyable<T> || is_nothrow_copy_assignable_v<T>).

4 Remarks: This operator is trivial if is_trivially_copy_constructible_v<T>, is_trivially_copy_assignable_v<T>, and is_trivially_destructible_v<T> are all true.

constexpr movable_box& operator=(movable_box&& rhs)
  noexcept(see below);

5 Effects: If this == addressof(rhs) is true, no effects. Otherwise:

  • (5.1) If movable<T> is modeled: if *this and rhs each contain a value, the contained value of *this is move-assigned from *rhs; otherwise, if rhs contains a value, a contained value is direct-non-list-initialized with std::move(*rhs); otherwise, the contained value of *this, if any, is destroyed. rhs.has_value() is unchanged.

  • (5.2) Otherwise: the contained value of *this, if any, is destroyed; then, if rhs contains a value, a contained value is direct-non-list-initialized with std::move(*rhs). If the initialization exits via an exception, *this contains no value. rhs.has_value() is unchanged.

6 Returns: *this.

7 Remarks: The exception specification is equivalent to: is_nothrow_move_constructible_v<T> && (!movable<T> || is_nothrow_move_assignable_v<T>).

8 Remarks: This operator is trivial if is_trivially_move_constructible_v<T>, is_trivially_move_assignable_v<T>, and is_trivially_destructible_v<T> are all true.

Observers [movable.box.obs]

constexpr bool has_value() const noexcept;
constexpr explicit operator bool() const noexcept;

1 Returns: true if and only if *this contains a value.

constexpr T& operator*() & noexcept;
constexpr const T& operator*() const & noexcept;

2 Preconditions: *this contains a value.

3 Returns: A reference to the contained value.

constexpr T&& operator*() && noexcept;
constexpr const T&& operator*() const && noexcept;

4 Preconditions: *this contains a value.

5 Returns: std::move(the contained value).

constexpr T* operator->() noexcept;
constexpr const T* operator->() const noexcept;

6 Preconditions: *this contains a value.

7 Returns: addressof(the contained value).

Drafting note: paragraphs [movable.box.assign] 1.1/1.2 and 5.1/5.2 reproduce, in standalone form, the semantics of [range.move.wrap] (1.3)/(1.4) together with the optional assignment semantics those paragraphs defer to when copyable<T>/movable<T> are modeled. Three of the four combined exception specifications reduce to the current ones: is_nothrow_copy_constructible_v<T> when copyable<T> is not modeled, is_nothrow_move_constructible_v<T> when movable<T> is not, and optional’s own is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T> when it is. The fourth does not: when copyable<T> is modeled the current wording defers to optional’s copy assignment, which carries no exception specification at all, so [movable.box.assign] p3 strengthens it. The strengthening is deliberate, and is one an implementation was already free to make under [res.on.exception.handling]; no currently conforming program can observe the difference. LWG review should confirm the other three equivalences and this one departure. The triviality Remarks on the assignment operators follow the conditions of [optional.assign] and keep movable_box<T> — and every view storing one — trivially copyable for trivially copyable, trivially assignable T; this is new relative to the exposition-only specification, whose triviality Remarks cover only the constructors and the destructor, and the reference implementation implements it.

7.3 [range.move.wrap]

Replace the contents of [range.move.wrap] as follows:

1 Many types in this subclause are specified in terms of an exposition-only class template movable-box. movable-box<T> denotes std::movable_box<T> ([movable.box]).

1 movable-box<T> behaves exactly like optional<T> with the following differences: […]

2 Recommended practice: […]

Drafting note: paragraphs 1.1–1.4 and 2 are struck in their entirety; their content is subsumed by [movable.box]. All uses of movable-box elsewhere in [ranges] are unchanged, as are the semantics of every view specified in terms of it. The operations clause [ranges] performs on movable-box — default construction, direct-initialization from const T& and T&&, in_place construction, the special member functions, operator*, operator-> (single_view::data), and has_value in preconditions — are all provided by [movable.box]; this claim should be re-audited against the working draft during wording review.

7.4 [version.syn]

Add:

#define __cpp_lib_movable_box 20XXXXL // also in <utility>

8 Open Questions for LEWG

  1. Header: <utility> or a new <movable_box> (D4)?
  2. Namespace: std or std::ranges (D3)?
  3. Are the value constructors correctly explicit and non-template (D6)?
  4. Does LEWG endorse the standalone specification (D5) and the usage-surface-only interface (D6, D9) as the scope of the paper?

9 Acknowledgements

Thanks to Eric Niebler for semiregular_box and the ranges design, and to the authors of [P2325R3] and [P2494R2], whose work converged this type to its current, stable form.

10 References

[beman.movable_box] Steve Downey. beman.movable_box: A non-exposition-only movable-box.
https://github.com/bemanproject/movable_box
[P2325R3] Barry Revzin. 2021-05-14. Views should not be required to be default constructible.
https://wg21.link/p2325r3
[P2494R2] Michał Dominiak. 2022-07-13. Relaxing range adaptors to allow for move only types.
https://wg21.link/p2494r2
[P3019R11] Jonathan Coe, Antony Peacock, Sean Parent. 2024-11-23. Vocabulary Types for Composite Class Design.
https://wg21.link/p3019r11
[range.move.wrap] Working Draft, Programming Languages — C++, [range.move.wrap].
https://eel.is/c++draft/range.move.wrap
[range-v3] Eric Niebler. range-v3: Range library for C++14/17/20.
https://github.com/ericniebler/range-v3