C++ Logo

std-proposals

Advanced search

Re: [std-proposals] all bytes zero == null object

From: Lorand Szollosi <szollosi.lorand_at_[hidden]>
Date: Mon, 29 Jul 2024 01:43:08 +0200
Hi,

Say you have a type called Handle that can be in null state, without
further optional<Handle> or Handle* or any other wrapper.
I think you're not looking for optional<Handle> to be redefined, that's an
XY-problem: a solution pressed instead of a problem stated.
What you're looking for is a concept that tells that a class implements the
Maybe monad:

template<class T>
concept maybe_impl = requires(T t) {
    { *t, bool(t), bool(!t), T{}; };
};


Then, wherever you were to use optional<T>, you need to use maybe_impl
auto, e.g.:

auto transform(maybe_impl auto t, auto f) ->
decltype(deduce_applied_type(t, f)) { // deduce_applied_time omitted here
as it's not important for the discussion
    if (t) {
        return f(t);
    }
    return {};
}

It also shows that perhaps transform() and value_or() are not exactly in
the proper place for optional<>, similarly transform() and
transform_error() and and_then() and or_else() are not exactly for
expected<> only. Instead, these could be free functions, or even named
operators, that could be applied for: (where applicable)

   - T*
   - unique_ptr<T>
   - shared_ptr<T>
   - optional<T>
   - expected<T, E>
   - any user-defined type with proper overloads.

What do you think?

Thanks,
-lorro

On Sun, Jul 21, 2024 at 6:51 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> 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.
>
> 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).
>
> 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) {
> . . .
> };
>
> Marking a class as 'null_bytes==null_object' means that a byte-pattern
> of all zeroes is a 'null object'. The standard header <type_traits>
> would then have "std::is_null_bytes_null_object".
>
> So then "std::optional::has_value" could be:
>
> bool optional::has_value(void) noexcept requires
> is_null_bytes_null_object_v<T>
> {
> return (0u == *storage) && !memcmp(storage, storage + 1u,
> sizeof(storage) - 1u);
> }
>
> Another effect of marking a class as 'null_bytes==null_object' would
> be that the destructor doesn't get called. For instance the following
> program won't crash:
>
> struct T (null==0) {
> int a, b, c;
> ~T(void) { *(int*)nullptr = 666; }
> };
>
> int main(void)
> {
> T var = {0,0,0};
> }
>
> The above program doesn't have undefined behaviour, because the
> destructor is treated as though it were written:
>
> ~T(void)
> {
> char const *const p = (char*)this;
> if ( (0u == *p) && !memcmp(p, p + 1u, sizeof(*this) - 1u) )
> return;
> *(int*)nullptr = 0;
> }
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2024-07-28 23:43:21