Proposal: Remove Lifetime Methods

Note

Feature awaiting further design consideration.

This is a proposal to remove lifetime methods and eliminate the concept of an object having a ‘dead’ state.

Rationale

Lifetime methods add significant complexity to the language, and the dead state is particularly something that has to be defined carefully.

Due to the inner/outer separation, method calls to __move and __destroy don’t have any effect in the dead state.

Changes

This proposal aims to remove lifetime methods by ensuring that all accessible objects are always in a live state.

Move

Currently it’s possible to create a dead state by moving out of a variable:

void f(Object value) {
        g(move value);
        // 'value' now in 'dead' state.
}

This usage of move is reasonable and useful; this proposal therefore suggests that value cannot be accessed after a move operation:

void f(Object value) {
        g(move value);

        // ERROR: cannot access variable 'value' after move.
        value.method();
}

move can also be used on arbitrary reference-typed values:

void method() {
        f(move @memberVariable);
}

This proposal suggests to make such usage illegal; move can only be used on local variables (instead mechanisms a swap() function could be used).

Ultimately it would be ideal to be able to use move on all references, followed by some kind of re-assignment. For example, the following is very likely safe:

void method() {
        f(move @memberVariable);
        new(&@memberVariable) Object();
}

So future work would allow the compiler to prove this is safe.

Assignment

The status of assignment is currently unclear; this proposal clarifies it by defining it as a normal method of a class:

class Object {
        void assign(Object value) noexcept;
}

Importantly this means variables cannot be assigned after they have been moved. A new operator might be used for this case:

void f(Object value) {
        g(move value);
        value := Object();
}

An alternative is to keep using placement new:

void f(Object value) {
        g(move value);
        new(&value) Object();
}

Destructor

Destructors would no longer implicitly check for the dead state, since they would be guaranteed to only be run once for each object.

islive/setdead/isvalid/setinvalid

These are no longer needed and can be removed.

Containers

Due to the changes containers must now provide a way to extract elements; their erasing methods would return the element being removed. For example, std::varray<T>::pop_back() would now return a T.

Ranges

The existing ranges still work, but there may need to be new range types (which could be called ‘generators’) that support extracting elements:

template <typename T>
interface input_generator {
        bool empty() const;
        T pop_front();
}

template <typename T>
interface bidirectional_generator {
        bool empty() const;
        T pop_front();
        T pop_back();
}

These interfaces match the containers themselves. As part of these changes, output_range could become output_generator:

template <typename T>
interface output_generator {
        void push_back(T value);
}