Search code examples
c++move-semanticsstd-expected

How can I use `.value()` on `std::expected<T, E>` where `T` is a move-only type?


The following code:

#include <chrono>
#include <expected>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility>

using namespace std::literals;

class Resource {

  Resource() { /* some setup, might raise */ }

  public:
  static std::expected<Resource, std::string> tryCreate() noexcept {
    try {
      return Resource();
    } catch (const std::runtime_error& exc) {
      return std::unexpected(exc.what());
    }
  }
  static const Resource create() noexcept {
    while (true) {
        const auto resource{Resource::tryCreate()};
        if (resource.has_value()) return resource.value();  // <-- this is no good.
        else std::this_thread::sleep_for(1s);
    }
  }
  Resource(const Resource&) = delete;
  Resource(Resource&& other) noexcept {}
  Resource& operator=(const Resource&) = delete;
  Resource& operator=(Resource&& other) noexcept { return *this; }
  ~Resource() { /* some cleanup */ }

  void use() const noexcept {}
};

int main() noexcept {
    const auto resource{Resource::create()};
    resource.use();
}

... refuses to compile with the error:

<source>:25:42: error: call to deleted constructor of 'const Resource'
   25 |         if (resource.has_value()) return resource.value();
      |                                          ^~~~~~~~~~~~~~~~
<source>:29:3: note: 'Resource' has been explicitly marked deleted here
   29 |   Resource(const Resource&) = delete;
      |   ^
1 error generated.
Compiler returned: 1

I don't see any explanation on cppreference that move-only types aren't allowed, and I'm not immediately able to find anyone else having a similar problem while searching the internet, (though I'm still new to this, so it's probable my C++ google-fu is inadequate). I think it might be something to do with "copy elision" but truly I'm quite lost.

Is it not possible to use a move-only type with std::expected? If it is, how can I modify my example code such that it works (without defining copy constructors)?

godbolt - fails on Clang with -std=c++23, but I'm experiencing similar with GCC 14.2.1, again with -std=c++23.


Solution

  • There is an rvalue overload of value() which returns an rvalue reference. So you can write return std::move(resource).value();. But for that, the local resource variable must not be const.