Invasive/in-band signaling can and is used here and there in C++.

Just create a type trait, not a core language feature, and template classes can use this property.

 

But

 - sometimes the free value is not stored as binary zero

 - open question: may classes like optional just use that property? Perhaps the null is already used for something else by the class itself or by others

 - You would need lots of different in-band-signals, perhaps the next class wants to store more than one possible value

 - It saves memory, but could also slow down some accesses, they could need additional checks, decoding

-----Ursprüngliche Nachricht-----
Von: Frederick Virchanza Gotham via Std-Proposals <std-proposals@lists.isocpp.org>
Gesendet: So 21.07.2024 18:51
Betreff: [std-proposals] all bytes zero == null object
An: std-proposals <std-proposals@lists.isocpp.org>;
CC: Frederick Virchanza Gotham <cauldwell.thomas@gmail.com>;
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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals