我能否将一个不可移动和不可复制的函数结果拷贝到一个可选类型中?

27

我想在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();

在不改变 Foofoo() 的情况下;

由于拷贝省略(copy elision),我已经可以做到这一点:

Foo f1 = foo();

这也可以编译通过,因为std::optional只需要存储的类型可析构:

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

但是我无法用函数结果填充f2

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

显然,这将需要复制或移动 Foo这很可能是不可能的,但我有没有忽略什么?

3个回答

28

你可以创建一个类,在其中使用转换运算符调用函数并返回结果。由于转换运算符会创建一个临时值,因此您不需要将类型设置为可复制/可移动:

您可以创建一个类,在其中使用转换运算符调用函数并返回结果。由于转换运算符会创建一个临时值,因此您不需要将类型设置为可复制/可移动:

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});

2
这里有一个实时演示:https://godbolt.org/z/j8d7nTvdv。看起来运行良好。 - bitmask
1
为什么要使用 static_cast<F&&>?不使用貌似也能工作:https://godbolt.org/z/1nKP513q4 - Solomon Ucko
3
@SolomonUcko 完美转发不会有任何问题。可以想象一种情况,即调用需要在rvalue上进行(例如与call_wrapper本身一起使用,因为它只能被调用一次),或者可能rvalue调用更有效率(std :: move(f)()f窃取)。 - Artyer
3
请注意,规定这种省略的规则并没有完全明确;CWG 2327 - Brian Bi
2
我会使用std::forward<F>(f)()而不是static_cast<F&&>(f)()。效果相同,但它清楚地表明您正在进行完美转发。其次,请注意,此技术不支持所有类型--该类型不能具有接受call_wrapper<F>的构造函数。这对于某些行为不良的类型(如C++11的std::function)会发生(后续版本的行为更好)。 - Yakk - Adam Nevraumont

14

我们可以利用C++23中新增的transform函数,该函数支持保证省略(guaranteed elision):

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

这比call_wrapper稍微一般一些,因为即使在Foo可以从call_wrapper中构造的情况下也可以使用(例如,它具有接受任何东西的转换构造函数模板)。


1
是的,我认为这将是C++23的正确方向。 - bitmask

6

不确定这是否符合您不修改Foofoo的要求,但您可以使用一个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();
}

我认为标题的答案是否定的。您无法将Foo复制或移动到一个optional中,但是通过包装器,您可以就地构造它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接