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.

bolov
  • 72,283
  • 15
  • 145
  • 224
cogle
  • 997
  • 1
  • 12
  • 25
  • 1
    I think in your case the `error_` type can be `std::optional`. This will massively simplify your design. – bolov Jul 20 '20 at 19:59
  • Yes, I don't disagree that using `std::optional` 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`, 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. – cogle Jul 20 '20 at 20:19
  • related: https://stackoverflow.com/questions/58288225/can-stdbyte-replace-stdaligned-storage – bolov Jul 20 '20 at 20:29

1 Answers1

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++.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • 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. – cogle Jul 20 '20 at 21:59