正确使用 std::launder

cog*_*gle 5 c++ c++17

我想确认我正确使用std::launder(...)确保我正确理解它的用法。

我正在Result<U,E>基于 Rust 的实现在 C++ 中创建一个。

template <typename E>
class ResultStorage<void, E, std::enable_if_t<std::is_trivially_destructible_v<E>>> {
    using type = typename std::aligned_storage<sizeof(E), alignof(E)>::type;

public:
    explicit constexpr ResultStorage(const Ok<void>&) noexcept : tag_(ResultTag::OK) {}

    explicit constexpr ResultStorage(const Ok<void>&&) noexcept : tag_(ResultTag::OK) {}

    explicit constexpr ResultStorage(const Err<E>& err) noexcept(std::is_nothrow_copy_constructible<E>())
        : tag_(ResultTag::ERR) {
        new (&error_) E(err.get_error());
    }
    explicit constexpr ResultStorage(const Err<E>&& err) noexcept(std::is_nothrow_move_constructible<E>())
        : tag_(ResultTag::ERR) {
        new (&error_) E(std::move(err.get_error()));
    }

    ~ResultStorage() = default;

    [[nodiscard]] constexpr E& get_error() & noexcept {
        assert_err(tag_);
        return *std::launder(reinterpret_cast<E*>(&error_));
    }
    
    // Code omitted for brevity
private:
    ResultTag tag_;
    type error_;

    template <typename Rv, typename Ev>
    friend class result::Result;
};
Run Code Online (Sandbox Code Playgroud)

在我using type = typename std::aligned_storage<sizeof(E), alignof(E)>::type;用作存储类型的代码中。我相信std::launder(...)当我从函数返回错误类型时需要使用,如下所示:

    [[nodiscard]] constexpr E& get_error() & noexcept {
        assert_err(tag_);
        return *std::launder(reinterpret_cast<E*>(&error_));
    }
Run Code Online (Sandbox Code Playgroud)

我认为我需要使用std::launder(...)的原因是因为传入的错误类型可能是一个可能带有const值的结构,那么看起来如果我不使用std::launder(...)那么在第一次初始化时它将引用const成员值,如果我要重用这个分配的存储将始终引用初始const成员值。

std::launder对什么情况需要它的使用有一个基本的了解,我们将不胜感激。我已经查看了这个函数的 cppreference,但仍然觉得它很神秘。

注意:完整的实现可以在github上找到。

bol*_*lov 3

我不会冒险猜测std::launder,但您的代码有另一个问题,解决它将std::launder不再需要:

new (&error_) E(err.get_error());
(reinterpret_cast<E*>(&error_);
Run Code Online (Sandbox Code Playgroud)

这是错误的,因为标准保证创建的对象实际上仅从&error标准布局类型开始。我知道这在实践中实际上是不正确的一种特殊情况是当您具有多重继承时。

所以正确的做法是:

// class data member:
E* ptr = nullptr;

// then:
ptr = new (&error_) E(err.get_error());
Run Code Online (Sandbox Code Playgroud)

并且仅用于ptr访问存储的对象。这也将std::launder不再需要。

正如您所看到的,当您使用 C++ 时,会遇到很多微妙的陷阱。