# How absl::StatusOr<T> Handles Move Semantics and Avoids Unnecessary Copies

> Explore how absl::StatusOr<T> optimizes performance by properly handling move semantics and avoiding redundant copies using its smart base class design.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: internals
- Published: 2026-07-13

---

**`absl::StatusOr<T>` implements move semantics through a carefully designed base class hierarchy that stores data in a union and uses `MaybeMoveData()` to ensure the wrapped value is moved exactly once, while specialized base classes handle reference types without copying.**

The `absl::StatusOr<T>` class in the Abseil C++ library (abseil/abseil-cpp) provides a discriminated-union-like wrapper that either contains an `absl::Status` error or a value of type `T`. Understanding how it handles move semantics is crucial for writing high-performance C++ code that avoids unnecessary copies when propagating return values.

## Design Overview: Base Class Architecture

The implementation separates storage concerns from public interface generation through a hierarchy of internal base classes defined in [`absl/status/internal/statusor_internal.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/internal/statusor_internal.h). This design ensures that moving a `StatusOr<T>` costs no more than moving the underlying type `T` itself.

The architecture consists of three key components:

- **`internal_statusor::StatusOrData<T>`** – Owns the union storage and implements the core move logic
- **`internal_statusor::MoveCtorBase<T>`** – Provides the public move constructor
- **`internal_statusor::MoveAssignBase<T>`** – Provides the public move-assignment operator

## Union Storage and Active Member Management

`StatusOrData<T>` stores both the value and status in a union, ensuring that only the active member is constructed or destroyed at any time. This eliminates the possibility of copying inactive data during move operations.

When a `StatusOr<T>` is moved, the constructor in `StatusOrData` (lines 78-84 in the internal header) performs the following:

```cpp
StatusOrData(StatusOrData&& other) noexcept {
  if (other.ok()) {
    MakeValue(other.MaybeMoveData());   // moves the stored T
    MakeStatus();                       // re-creates an OK status
  } else {
    MakeStatus(std::move(other.status_)); // moves the error status
  }
}

```

Because the storage is a union, the move operation only touches the active member. If the source contains a value, it moves that value; if it contains an error, it moves the `absl::Status` instead.

## The MaybeMoveData() Optimization

The `MaybeMoveData()` method centralizes the conditional move logic to ensure the value is moved exactly once. This helper distinguishes between reference and non-reference types:

- **For non-reference types**: Returns `std::move(data_)` to invoke `T`'s move constructor
- **For reference types**: Returns the referenced value unchanged (stored as `Reference<T>`), avoiding any copy or move

This specialization ensures that even when `T` is a reference, the wrapper remains lightweight and copy-free.

## Public Interface: Move Constructors and Assignment

The public move operations are deliberately thin, forwarding to the optimized implementations in `StatusOrData`. According to the source code in [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h):

- **`MoveCtorBase<T>`** provides the move constructor that simply forwards to `StatusOrData`'s move constructor
- **`MoveAssignBase<T>`** provides the move-assignment operator that forwards to `StatusOrData`'s move-assignment

Both operations are marked `noexcept`, allowing the compiler to apply NRVO (Named Return Value Optimization) and other move-friendly optimizations. This design guarantees that code like `absl::StatusOr<Heavy> b = std::move(a);` performs exactly one move of the underlying `Heavy` object.

## Accessing Values Without Copying

The accessor operators ensure users cannot accidentally trigger copies when retrieving values:

- **`operator*`** returns `T&` (or `const T&`) directly
- **`operator->`** returns `T*` to the stored value

These methods only access the data when `ok()` returns true, providing zero-overhead value access. Users should always dereference `StatusOr` using these operators rather than copying the value out.

## Practical Example

The following code demonstrates that moving a `StatusOr<T>` only moves the underlying value once, with no extra copies introduced by the wrapper:

```cpp
#include "absl/status/statusor.h"
#include "absl/status/status.h"
#include <iostream>

struct Heavy {
  Heavy() = default;
  Heavy(const Heavy&) { std::cout << "copy\n"; }
  Heavy(Heavy&&) noexcept { std::cout << "move\n"; }
};

absl::StatusOr<Heavy> MakeHeavy(bool succeed) {
  if (succeed) return Heavy();               
  return absl::Status(absl::StatusCode::kInvalidArgument,
                      "construction failed");
}

int main() {
  // Move-construct a StatusOr – only one "move" of Heavy should be printed.
  absl::StatusOr<Heavy> a = MakeHeavy(true);
  absl::StatusOr<Heavy> b = std::move(a);    // moves the stored Heavy

  // Move-assign a StatusOr that already holds a value.
  absl::StatusOr<Heavy> c = MakeHeavy(true);
  absl::StatusOr<Heavy> d = MakeHeavy(true);
  d = std::move(c);                          // moves the stored Heavy

  // Access without copying.
  if (b.ok()) {
    const Heavy& ref = *b;   // no copy, just a reference
  }
}

```

Expected output:

```

move        // from MakeHeavy returning a temporary Heavy
move        // from std::move(a) into b
move        // from std::move(c) into d

```

The output confirms that **only moves** occur; the `StatusOr` wrapper adds no additional copies.

## Summary

- **`absl::StatusOr<T>`** uses union storage in `internal_statusor::StatusOrData` to ensure only active members are moved
- **`MaybeMoveData()`** ensures the value is moved exactly once, with special handling for reference types
- **Thin public base classes** (`MoveCtorBase` and `MoveAssignBase`) forward to optimized implementations while maintaining `noexcept` guarantees
- **Direct accessors** (`operator*` and `operator->`) prevent accidental copies when retrieving values
- All move operations are `noexcept`, enabling compiler optimizations like NRVO

## Frequently Asked Questions

### What happens when I move a StatusOr that contains an error?

When the source `StatusOr` does not contain a value (`!ok()`), the move constructor moves the `absl::Status` object instead of the value. This is implemented in `StatusOrData`'s move constructor by calling `MakeStatus(std::move(other.status_))`, ensuring the error payload is transferred without copying the status message or payload.

### Does StatusOr support move-only types?

Yes, `absl::StatusOr<T>` fully supports move-only types. Because the implementation uses `std::move` via `MaybeMoveData()` and the move constructors are not constrained by copy requirements, you can store `std::unique_ptr` or other move-only types in a `StatusOr`. The reference specialization ensures that even reference types don't require copyability.

### How does StatusOr avoid copying when T is a reference type?

When `T` is a reference, `absl::StatusOr<T>` stores a lightweight `Reference<T>` wrapper instead of the value directly. The `MaybeMoveData()` method detects this and returns the reference unchanged rather than attempting to move it, eliminating any copy or move overhead while maintaining reference semantics.

### Are StatusOr move operations noexcept?

Yes, the move constructor and move-assignment operator are marked `noexcept`. This is possible because `absl::Status` provides noexcept move operations and the value move is invoked via `std::move` without throwing. The noexcept guarantee allows the compiler to optimize around these operations, particularly when applying Named Return Value Optimization (NRVO) in functions returning `StatusOr<T>`.