[llvm] [libc++] Ensure that `std::expected` has no tail padding (PR #69673)

Jan Kokemüller via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 23 22:44:40 PDT 2023


jiixyj wrote:

> Perhaps we should use a mixed strategy:
> 
>     * when the `bool` flag lives in the tail padding of the union, the repr strategy should be used, and `expected` shouldn't have reusable tail padding,
> 
>     * otherwise, however, we should make the tail padding of `expected` (if any) reusable.
> 
> 
> It's painful that we have no `[[no_unique_address(condition)]]` yet.

What do you think about this:

```c++
#include <expected>
#include <iostream>
#include <memory>
#include <optional>

template <typename Val, typename Err>
union expected_union {
    [[no_unique_address]] Val val;
    [[no_unique_address]] Err unex;
};

template <typename Val, typename Err, bool StuffTail = false>
struct expected_repr {
   private:
    expected_union<Val, Err> union_;
    [[no_unique_address]] bool has_val_;
};

template <typename Val, typename Err>
struct expected_repr<Val, Err, true> {
   private:
    [[no_unique_address]] expected_union<Val, Err> union_;
    [[no_unique_address]] bool has_val_;
};

template <typename Val, typename Err,  //
          typename Repr = expected_repr<Val, Err, true>,
          typename Union = expected_union<Val, Err>>
concept tail_stuffable = sizeof(Repr) == sizeof(Union);

template <typename Val, typename Err>
struct expected_base {
   protected:
    [[no_unique_address]] expected_repr<Val, Err, false> repr_;
};

template <typename Val, typename Err>
    requires tail_stuffable<Val, Err>
struct expected_base<Val, Err> {
   protected:
    expected_repr<Val, Err, true> repr_;
};

template <typename Val, typename Err>
struct expected : private expected_base<Val, Err> {};
```

https://godbolt.org/z/5oj6TjTGd

You would have two `repr` types, depending on whether the bool flag can live in the union's tail padding or not, and switch between them with inheritance (sort of emulating conditional `[[no_unique_address]]`).

What I like about this is that you don't even need something like `std::__libcpp_datasize`. You can just ask the first `repr` type: "Can the `bool` be stuffed in your tail?"

In the "tail padding" case you would `std::destruct_at`/`std::construct_at` the whole `repr`, including the "has value" flag. In the "normal" case you would only `std::destruct_at`/`std::construct_at` the union and set the "has value" flag manually.

I'll try to implement this to see how bad it is regarding code complexity. But I'm optimistic!

https://github.com/llvm/llvm-project/pull/69673


More information about the llvm-commits mailing list