5

I am wanting to confirm that I am correctly using std::launder(...) ensuring that I correctly understand its usage.

I am creating a Result<U,E> in C++ based off of Rust's implementation.

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

In the code I use using type = typename std::aligned_storage<sizeof(E), alignof(E)>::type; as my storage type. It is my belief that I need to use std::launder(...) when I return the error type from the function like so:

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

The reason that I believe I need to use std::launder(...) is because since the incoming error type may be a struct possibly with a const value then it appears that if I do not use std::launder(...) then on first initialization it will refer to the const member value and if I was to reuse this allocated storage it would always refer to the initial const member value.

I have a rudimentary understanding of std::launder so an explanation of what circumstances require its usage would be appreciated. I have looked at the cppreference for this function but still find it rather mystifying.

Note: the full impl can be found on github.

3
  • 1
    I think in your case the error_ type can be std::optional<E>. This will massively simplify your design. Commented Jul 20, 2020 at 19:59
  • Yes, I don't disagree that using std::optional<E> would be simpler. I didn't do so as I wanted to try and implement something from "scratch" with minimal help from the standard library as possible. I certainly thought about using std::optional<E>, but thought this would be an interesting manner to implement it and profile the resulting implementation. Edit: In the end it did lead to this question and so I think from a learning perspective it achieved what I desired. Commented Jul 20, 2020 at 20:19
  • related: stackoverflow.com/questions/58288225/… Commented Jul 20, 2020 at 20:29

1 Answer 1

3

I won't venture a guess about std::launder, but your code has another problem and solving it would make std::launder not needed anymore:

new (&error_) E(err.get_error());
(reinterpret_cast<E*>(&error_);

This is wrong because the standard guarantee that the created object will actually start at &error only for standard layout types. One particular case where I know this actually won't be true in practice is when you have multiple inheritance.

So the correct way is:

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

// then:
ptr = new (&error_) E(err.get_error());

And only use ptr to access the stored object. This will also make std::launder not needed anymore.

As you see there are a lot of subtle pitfalls when you go this low level with C++.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for pointing this out, rereading the page on std::aligned_storage I encountered another potential bug in the code. I will specialize on std::is_standard_layout and only use the code in the question when it is true.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.