You could have the trait and functions the user could specialize to invalid bit patterns about the type:

template<class T> struct std::has_invalid_bit_pattern : public std::false_type {}; 
template<class T> void std::set_invalid_bit_pattern(std::byte* data) {}
template<class T> bool std::is_invalid_bit_pattern(const std::byte* data) { return false; }

Then std::optional could use those traits to check and set those bit patterns. It could be useful for many other containers too.

Breno G.


On Mon, Jul 22, 2024 at 10:59 AM Thiago Macieira via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Sunday 21 July 2024 09:51:17 GMT-7 Frederick Virchanza Gotham via Std-
Proposals wrote:
> Typically when implementing a class such as 'std::optional', the size
> of 'std::optional<T>' is equal to "sizeof(T) + 1u", because one more
> byte is needed to store a boolean to indicate whether there is a valid
> object in existence.

That means it's 2*sizeof(T) in total size.

> We can use "__datasizeof" or "[[no_unique_address]]" in order to try
> use the tail padding of T to store the boolean, and if we succeed,
> then sizeof(optional<T>) == sizeof(T).

QoI. We don't need a core language change for this.

> If T has no tail padding, then we need an extra byte to store the
> boolean. Unless . . . we had the ability to mark the class
> 'null_bytes==null_object" as follows:
>
>     class T (null_bytes==null_object) {
>         . . .
>     };

No, we cannot do that. Let's take the example of a null pointer, which in most
architectures is a bitwise zero. A std::optional<T *> containing a null
pointer is not semantically the same thing as a disengaged std::optional. For
some tasks it may be important to return "any pointer including null" and
differentiate from "sorry, I failed".

Let me take another example: QString. Its null-bytes representation is simply
d = nullptr, ptr = nullptr and size = 0, which is the representation of a
default QString(). So same as the pointer case above, this is not the same as
a disengaged std::optional<QString>.

On the other hand, a std::optional<std::errc> could use the value zero as
"disengaged" because that is not a value used by the enumeration. Though most
likely, that would be used by std::expected instead of optional, where
semantically it means something.

I like the idea of a compact representation, but it needs to be informed by a
traits class. Ideally it would allow the trait to inspect the bytes to
determine validity, but that is going to run into a discussion on how to do it
without UB on the storage without having constructed the object there. It
might not be necessary to inspect all bytes of the object, for one thing.

Needless to say, this needs to be opt-in, not opt-out.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
  Principal Engineer - Intel DCAI Platform & System Engineering



--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals