27

I want to store a non-trivial type that is immovable and not copyable in an std::optional. However the object is constructed by a free function. (example)

struct Foo {
    Foo();
    Foo(Foo const&) = delete;
    Foo(Foo&&) = delete;
    Foo& operator=(Foo const&) = delete; // added for completeness
    Foo& operator=(Foo&&) = delete; // added for completeness
    ~Foo();
};
Foo foo();

Without changing Foo or foo();

Thanks to copy elision, I can already do this:

Foo f1 = foo();

And this also compiles, because std::optional only requires the stored type to be destructible:

std::optional<Foo> f2;
f2.emplace();

But I cannot fill f2 with a function result:

f2 = foo(); // no
f2.emplace(foo()); // no

Obviously, because this would require copies or moves of Foo. This is likely impossible, but have I overlooked something?

bitmask
  • 32,434
  • 14
  • 99
  • 159

3 Answers3

28

You can make a class where a conversion operator calls the function and returns the result. Since the conversion operator will create a prvalue, you don't need the type to be copyable/movable:

template<typename F>
struct call_wrapper {
    F&& f;

    constexpr operator decltype(auto)() && {
        return static_cast<F&&>(f)();
    }
};
template<typename F>
call_wrapper(F&&) -> call_wrapper<F>;


std::optional<Foo> f2;
f2.emplace(call_wrapper{foo});
Artyer
  • 31,034
  • 3
  • 47
  • 75
  • 2
    [Here's a live demo](https://godbolt.org/z/j8d7nTvdv). Seems to work well. – bitmask Jan 10 '23 at 10:21
  • 1
    Why `static_cast`? Seems to work without it: https://godbolt.org/z/1nKP513q4 – Solomon Ucko Jan 10 '23 at 17:51
  • 3
    @SolomonUcko Perfect forwarding doesn't hurt. You can imagine a situation where the call needs to be on an rvalue (Like with call_wrapper itself because is meant to only be called once) or maybe an rvalue call is more efficient (`std::move(f)()` stealing from `f`) – Artyer Jan 10 '23 at 19:33
  • 3
    Note that the rules that govern such an elision are not fully specified; [CWG 2327](https://wg21.link/cwg2327) – Brian Bi Jan 11 '23 at 00:15
  • 2
    I'd use `std::forward(f)()` instead of `static_cast(f)()`. The effect is identical, but it makes it clear you are doing perfect forwarding. Second, note that this technique doesn't support all types -- the type cannot have a constructor that accepts `call_wrapper`. This happens for some ill-behaved types, like C++11's std::function (later versions behave better). – Yakk - Adam Nevraumont Jan 11 '23 at 15:42
14

We can take advantage of transform (new in C++23), which supports guaranteed elision:

auto f = std::optional(1).transform([](int) { return foo(); });

This is slightly more general than call_wrapper because it works even in cases where Foo would constructible from call_wrapper (because, for instance, it has a converting constructor template that accepts everything).

T.C.
  • 133,968
  • 17
  • 288
  • 421
6

Not sure if this fits your requirement of not modifying Foo or foo, but you can use a Bar wrapper that default initializes a Foo member by calling foo:

#include <optional>

namespace {
    struct Foo {
        Foo() = default;
        Foo(Foo const&) = delete;
        Foo(Foo&&) = delete;
        Foo& operator=(Foo const&) = delete;
        Foo& operator=(Foo&&) = delete;
        ~Foo() {}
    };
    Foo foo() { return {}; }

    struct Bar {
        Foo f = foo();
    };
}

int main() {
    std::optional<Bar> f3;
    f3.emplace();
}

I think the answer to the title is No. You cannot copy or move a Foo into an optional, but via the wrapper you can construct it in place.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185