我可以将不可移动且不可复制的函数结果复制删除为可选值吗?

bit*_*ask 28 c++ copy-elision stdoptional

我想存储一个不平凡的类型,它是不可移动且不可复制的std::optional。然而该对象是由自由函数构造的。(例子

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();
Run Code Online (Sandbox Code Playgroud)

无需改变Foofoo()

感谢复制省略,我已经可以做到这一点:

Foo f1 = foo();
Run Code Online (Sandbox Code Playgroud)

这也可以编译,因为std::optional只要求存储的类型是可破坏的:

std::optional<Foo> f2;
f2.emplace();
Run Code Online (Sandbox Code Playgroud)

但我无法填写f2函数结果:

f2 = foo(); // no
f2.emplace(foo()); // no
Run Code Online (Sandbox Code Playgroud)

显然,因为这需要复制或移动Foo. 这可能是不可能的,但我是否忽略了一些事情?

Art*_*yer 30

您可以创建一个类,其中转换运算符调用函数并返回结果。由于转换运算符将创建纯右值,因此您不需要该类型是可复制/可移动的:

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});
Run Code Online (Sandbox Code Playgroud)

  • @SolomonUcko 完美转发不会有什么坏处。你可以想象这样一种情况:调用需要在右值上(就像 call_wrapper 本身一样,因为它只被调用一次)或者右值调用可能更有效(`std::move(f)()` 窃取`f`) (3认同)
  • 请注意,管理此类省略的规则尚未完全指定;[CWG 2327](https://wg21.link/cwg2327) (3认同)
  • 我会使用 `std::forward&lt;F&gt;(f)()` 而不是 `static_cast&lt;F&amp;&amp;&gt;(f)()`。效果是相同的,但它清楚地表明您正在进行完美的转发。其次,请注意,此技术并不支持所有类型 - 该类型不能具有接受“call_wrapper&lt;F&gt;”的构造函数。这种情况发生在某些行为不当的类型上,例如 C++11 的 std::function (更高版本的行为更好)。 (3认同)
  • [这是一个现场演示](https://godbolt.org/z/j8d7nTvdv)。看起来效果不错。 (2认同)

T.C*_*.C. 15

我们可以利用transform(C++23 中的新功能),它支持保证省略:

auto f = std::optional(1).transform([](int) { return foo(); });
Run Code Online (Sandbox Code Playgroud)

这比它稍微通用一些,call_wrapper因为它甚至可以在Foo可以构造的情况下工作call_wrapper(因为,例如,它有一个接受所有内容的转换构造函数模板)。


for*_*818 6

不确定这是否符合您不修改Fooor的要求foo,但您可以使用Bar默认Foo通过调用初始化成员的包装器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();
}
Run Code Online (Sandbox Code Playgroud)

我认为标题的答案是否定的。您不能将 a 复制或移动Foo到 an 中optional,但通过包装器您可以就地构建它。