Skip to content

Instantly share code, notes, and snippets.

@elazarg
Last active April 24, 2025 01:02
Show Gist options
  • Save elazarg/ffe096f8e5c237d60a5d6f5205f50e2e to your computer and use it in GitHub Desktop.
Save elazarg/ffe096f8e5c237d60a5d6f5205f50e2e to your computer and use it in GitHub Desktop.

Document Number: PxxxxRx

Date: 2025-04-24
Audience: Library Evolution Working Group (LEWG), Evolution Working Group (EWG)
Reply-to: Elazar Gershuni [email protected]
Author: Elazar Gershuni (Technion)

PxxxxR0 - A Library-Based Mechanism for Moved-From Initialization (moved_from())

Abstract

This proposal introduces a standardized, opt-in library mechanism in C++ for constructing an object of type T directly into a moved-from state. It provides a free function template moved_from() that invokes a static factory method T::moved_from() defined by the type author, constrained by a concept SupportsMovedFrom. This facility aims to address performance issues and the use of unsafe workarounds in scenarios where an object is constructed solely to be immediately overwritten via assignment or mutation, leveraging the valid but unspecified nature of the moved-from state, while providing a stronger guarantee for optimization.

1. Introduction and Motivation

C++ programming frequently encounters patterns where an object is default-constructed only to have its state immediately replaced by a subsequent operation, such as assignment from another object, population via an out-parameter function, or deserialization. For types with non-trivial default constructors or those that acquire resources during default construction (even if minimal), this initial construction can introduce unnecessary overhead.

Current practices to avoid this overhead often involve techniques that bypass normal object construction, such as:

  • Using raw memory and placement new.
  • Employing ad-hoc, type-specific factory functions.
  • Relying on memcpy or similar operations on byte buffers.

These methods can be error-prone, harder to reason about, and may break type safety or RAII guarantees if not handled with extreme care. This places a burden on client code that needs to interact efficiently with such types, requiring users to understand and correctly apply these low-level or type-specific workarounds.

The C++ standard guarantees that a moved-from object is in a valid but unspecified state, is destructible, and is assignable. This proposal recognizes the moved-from state as a potential target state for initial construction when the object's final state will be established immediately thereafter. By providing a standard, opt-in mechanism, moved_from(), to construct directly into this state via a type-provided T::moved_from() static method, we propose to shift the burden of implementing this optimized, minimal initialization from the numerous points of client code to the single type implementer. This encapsulation allows the type author, who is the expert on the type's invariants and resource management, to provide a safe, correct, and efficient implementation once, rather than forcing every user to rediscover or reimplement potentially unsafe workarounds. Furthermore, by requiring T::moved_from() to produce a state with properties equivalent to that resulting from a standard move, we enable compilers to perform more aggressive optimizations.

By standardizing this pattern, we can:

  • Avoid redundant initialization costs: Eliminate the overhead of a full default construction when the state is ephemeral.
  • Replace unsafe hacks with idiomatic C++: Offer a type-safe, RAII-compatible alternative to manual memory management for this specific optimization, moving the complexity into the type definition.
  • Provide semantic clarity: Explicitly signal intent that an object is being created in a state ready to be populated or overwritten.
  • Enable efficient object reuse: Facilitate patterns where objects are constructed into a minimal state before acquiring significant resources or complex invariants via a subsequent step.
  • Enable enhanced compiler optimizations: By guaranteeing the properties of a standard moved-from state, compilers can potentially elide or optimize operations based on the known characteristics of that state.

This mechanism does not aim to replace general object construction or placement new but rather to provide a targeted solution for the specific case of construction into a state with the defined properties of a moved-from object.

2. Proposed Mechanism

The proposal introduces a concept and a function template within the <utility> standard library header. This header is chosen due to its existing role in housing utility functions such as std::move and std::forward, which conceptually align with the semantics of moved-from construction. Keeping the feature in <utility> promotes discoverability while avoiding the overhead of introducing a dedicated new header. The final choice should balance discoverability, cohesion, and minimal invasiveness.

// Proposed concept  
template<typename T>  
concept SupportsMovedFrom = requires {  
    { T::moved_from() } -> std::same_as<T>;  
};

// Proposed function template  
template<SupportsMovedFrom T>  
[[nodiscard]] T moved_from();

Type authors who wish to enable this optimization for their type T would define a public static member function T::moved_from(). This function is responsible for creating and returning an instance of T that is in a state with properties equivalent to an object of type T that has been the source of a move operation. The use of the SupportsMovedFrom concept ensures that calling moved_from() for a type T that does not provide the necessary T::moved_from() static member will result in a clear compile-time error, providing good diagnostics for misuse.

Example Implementation and Usage:

struct LargeBuffer {  
    std::unique_ptr<char[]> data;  
    size_t size = 0; // Example member

    // Default constructor might allocate a small initial buffer or perform checks  
    // LargeBuffer() : data(nullptr), size(0) {} // Potentially expensive

    // Destructor cleans up data  
    ~LargeBuffer() { data.reset(); }

    // Move constructor/assignment handle resource transfer  
    LargeBuffer(LargeBuffer&& other) noexcept  
        : data(std::move(other.data)), size(other.size) {  
        other.size = 0;  
    }  
    LargeBuffer& operator=(LargeBuffer&& other) noexcept {  
        if (this != &other) {  
            data = std::move(other.data);  
            size = other.size;  
            other.size = 0;  
        }  
        return *this;  
    }

    // Opt-in: Static factory for the moved-from state  
    // This implementation must now produce a state with properties equivalent  
    // to moving from a LargeBuffer. For LargeBuffer, this means data is nullptr  
    // and size is 0, ensuring destructibility and assignability.  
    static LargeBuffer moved_from() {  
        // Direct construction into moved-from state without redundant initialization  
        // Assumes LargeBuffer can be constructed with a null unique_ptr and zero size  
        // without triggering expensive default constructor logic if one exists.  
        // A type might need a dedicated private constructor or other means to achieve this.  
        LargeBuffer buf; // Using default constructor here for simplicity in example,  
                         // but ideal implementation avoids its cost if possible.  
        buf.data = nullptr;  
        buf.size = 0;  
        return buf;  
    }

    // Example function that populates a buffer  
    void populate(size_t new_size, const char* source_data) {  
        data = std::make_unique<char[]>(new_size);  
        std::memcpy(data.get(), source_data, new_size);  
        size = new_size;  
    }

    // Other members...  
     // Add a default constructor if needed for other use cases, ensuring moved_from bypasses its cost  
    LargeBuffer() : data(nullptr), size(0) { /* Potentially expensive setup */ }  
};

void process_data_into_buffer(const char* source, size_t source_size) {  
    // Instead of: LargeBuffer b; // Potentially expensive default construction  
    LargeBuffer b = moved_from<LargeBuffer>();

    // b is now in a state with properties equivalent to a moved-from LargeBuffer  
    // The subsequent populate call will overwrite this state.  
    b.populate(source_size, source);

    // b is used here...  
} // b is destructed properly

Type authors are responsible for ensuring T::moved_from() produces an object in a state with properties equivalent to an object of type T that has been the source of a move operation, as defined by the type's move constructor or move assignment operator. This state must be valid, destructible, and assignable, satisfying the requirements of a moved-from object as defined by the C++ standard [basic.stc.dynamic.move]. This equivalence pertains to the object's behavior regarding resource management, destruction, and assignment, not necessarily value equality.

3. Proposed Wording

Add the following to the header <utility>:

--- a/include/utility  
+++ b/include/utility  
@@ -XX,YY +XX,YY @@  
 namespace std {

   // ... existing content ...

+  // Z.Y Moved-from initialization [utility.moved_from]  
+  
+  template<typename T>  
+  concept SupportsMovedFrom =  
+    requires {  
+      { T::moved_from() } -> std::same_as<T>;  
+    };  
+  
+  template<SupportsMovedFrom T>  
+  [[nodiscard]] T moved_from();  
+  
+  template<SupportsMovedFrom T>  
+  [[nodiscard]] T moved_from() {  
+    return T::moved_from();  
+  }  
+  
+  // Z.Y.1 Requirements on SupportsMovedFrom [utility.moved_from.req]  
+  // A type T models SupportsMovedFrom only if the expression T::moved_from()  
+  // is a constant expression if and only if T::moved_from is a constexpr function.  
+  // An object of type T returned by T::moved_from() shall be in a state  
+  // with properties equivalent to an object of type T that has been the source  
+  // of a move operation. Such an object shall be valid, destructible, and assignable.  
+  // "Equivalence of properties" in this context refers to consistent destructibility,  
+  // assignability, and observable side-effects related to resource management,  
+  // but does not require semantic equality (e.g., via operator==) or identical  
+  // internal state representation.  
+  // [Note: Compilers are encouraged to leverage this equivalence of properties  
+  // to perform optimizations, such as eliding redundant operations on the  
+  // object's initial state before it is overwritten. — end note]  
+  
   // ... rest of <utility> ...

 } // namespace std

4. Rationale

  • The concept SupportsMovedFrom provides a clear, checkable contract for types opting into this mechanism.
  • The function template moved_from() offers a clean, generic interface for users.
  • Returning T by value allows for RVO/NRVO, making the direct usage T obj = moved_from(); efficient.
  • The responsibility for producing a state with properties equivalent to a standard moved-from state is explicitly placed on the type author's implementation of T::moved_from(). This stronger guarantee, compared to merely a "valid but unspecified" state, enables compilers to make stronger assumptions and perform more aggressive optimizations based on the known behavior of a moved-from object. The explicit definition of "equivalence of properties" in the wording clarifies this guarantee.
  • Placing the feature in <utility> groups it with other fundamental object manipulation utilities.
  • The explicit requirements in the wording clarify the guarantees provided by T::moved_from(), specifically detailing what the "equivalence of properties" means.

5. Design Considerations

  • Library-based: This approach requires no core language changes, making adoption simpler and less contentious than language extensions. It leverages existing features (concepts, static methods, move semantics).
  • Standard Library Applicability: Several standard library types could benefit from implementing moved_from(), including:
    • Containers: std::vector, std::list, std::map, etc.

    • Smart Pointers: std::unique_ptr, std::shared_ptr

    • Strings: std::string

    • Utility Types: std::optional, std::variant

      Implementing moved_from() for these types would provide a standardized way to obtain them in their well-defined moved-from states (e.g., empty for containers, null for smart pointers), enabling efficient initialization-for-overwrite patterns.

  • Constexpr support: The static member function T::moved_from() may be declared constexpr where appropriate, enabling compile-time moved-from construction in constexpr contexts. This is implicitly supported by the design.
  • Trait compatibility: A trait such as std::supports_moved_from could be defined in the future to detect support for moved_from(), aiding generic programming by enabling compile-time introspection and fallback strategies.
  • Opt-in: Only type authors who see a benefit and are willing to take on the increased responsibility of guaranteeing the properties equivalent to a standard moved-from state for T::moved_from() enable this feature, avoiding impact on types that do not need or cannot support it efficiently.
  • Static Member Function: A static factory method T::moved_from() is a natural place to define alternative construction logic specific to type T. It keeps the mechanism discoverable as part of the type's interface.
  • Leveraging Moved-From State: The core idea relies on the standard's definition of the moved-from state. By requiring equivalence of properties, the proposal strengthens this reliance to enable optimizations.
  • Return by Value: Returning T by value from both T::moved_from() and moved_from() is idiomatic and relies on RVO/NRVO for efficiency in the common assignment pattern (T obj = moved_from();). This avoids exposing references or pointers that could dangle or be misused.

6. Performance Considerations

A key motivation for this proposal is performance optimization in scenarios where an object is constructed only to be immediately overwritten. The performance benefit hinges entirely on the implementation of T::moved_from(). For types with expensive default constructors, a well-implemented T::moved_from() that directly establishes a minimal state with properties equivalent to a standard moved-from state should offer significant savings by avoiding redundant initialization.

Furthermore, the stronger guarantee of equivalence of properties allows compilers to perform more aggressive optimizations. Knowing that the object is in a state with predictable behavior regarding resource management and subsequent operations allows the compiler to potentially reason about its internal representation and optimize subsequent operations (like assignment) more effectively, potentially eliding checks or cleanup that would be necessary for an arbitrary "valid but unspecified" state.

To support this proposal, benchmark data demonstrating the performance difference between:

  • Default construction followed by assignment/population.
  • Using moved_from() followed by assignment/population.
  • Existing unsafe workarounds (e.g., placement new) where applicable.

...for representative types would significantly strengthen the case.

7. Testing Considerations

Type authors implementing T::moved_from() are responsible for ensuring it produces an object in a state with properties equivalent to an object that has been the source of a move operation, and that this state is valid, destructible, and assignable. Testing should focus on:

  • Equivalence of Properties: This is the primary focus. Tests should verify that the observable behavior of an object created via moved_from() is indistinguishable from an object that has been moved from, specifically regarding its destructibility, assignability, and resource management. This might involve checking if resource handles are released (e.g., pointers are null), if size/capacity are zero for containers, and verifying that subsequent operations like assignment behave as expected for a moved-from object. The focus is on behavioral consistency, not value equality.
  • Destructibility: Verify that an object created via moved_from() can be safely destructed without resource leaks or crashes.
  • Assignability: Verify that an object created via moved_from() can be correctly assigned to (both copy and move assignment if applicable).
  • Minimal Invariants: Verify any specific minimal invariants guaranteed by the type after a standard move operation are also present after calling moved_from().

Providing guidelines or examples of such tests in documentation would be beneficial for library authors adopting this feature.

8. Known Limitations

  • Increased Burden on Type Authors: Requiring equivalence of properties with a standard moved-from state places a higher implementation and testing burden on type authors compared to merely guaranteeing a "valid but unspecified" state.
  • Performance is Implementation-Defined: While equivalence of properties enables more potential optimizations, the actual performance benefit still hinges entirely on how efficiently T::moved_from() is implemented by the type author to achieve that state.
  • Compiler Enforcement: While the requirement for equivalence of properties is in the standard, compilers cannot fully verify this semantic property in all cases. It relies on the type author's correct implementation.
  • Increased API Surface: Type authors opting in must document and maintain the T::moved_from() method and ensure its equivalence of properties with the standard moved-from state.
  • Possible Semantic Coupling: Consumers relying on the guaranteed equivalence of properties might still implicitly couple their code to the specific moved-from state defined by the type, although this is now a stronger, more reliable contract regarding the object's behavior.

9. Alternatives Considered

  • Placement New: Rejected due to its low-level nature, potential for misuse (e.g., failing to call destructor), and lack of type-safety guarantees compared to a factory function returning a typed object.
  • Ad-hoc Factory Functions: Rejected because they lack standardization and discoverability. moved_from() provides a single, recognizable entry point constrained by a concept.
  • Language Extension: Rejected as unnecessary given the feature can be implemented using existing C++26 library features.
  • std::moved_from_or_default() Function: This alternative proposed a single function template that would call T::moved_from() if T models SupportsMovedFrom, and otherwise fall back to default constructing an object of type T (T()).
    • Reason for Rejection: This approach was rejected primarily because it undermines the performance motivation for types with expensive default constructors that do not opt into SupportsMovedFrom. Calling std::moved_from_or_default() on such a type would still incur the very initialization cost the proposal aims to avoid. Furthermore, it creates behavioral ambiguity for users, as the function's performance and exact initial state (optimized moved-from vs. default-constructed) would depend on an internal detail of the type. The compile-time error provided by the proposed moved_from() when T::moved_from() is missing is a clearer signal to the user that the optimized moved-from state is not available for that type.
    • Conceptual Implementation Example:
template<typename T>  
T moved_from_or_default() {  
    if constexpr (SupportsMovedFrom<T>) {  
        return T::moved_from(); // Calls the optimized factory  
    } else {  
        return T{}; // Falls back to default construction  
    }  
}
  • This example illustrates the conditional logic but highlights the issue: the user calls a function named moved_from_or_default but might still pay for a full default construction.

10. Acknowledgements

11. Revision History

R0: Initial proposal. [2025-04-24]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment