C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::sizeof_minus_trailing_padding

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sun, 3 Dec 2023 17:20:55 +0000
On Fri, Dec 1, 2023 at 11:38 PM Frederick Virchanza Gotham wrote:
>
> I think I just managed to write '__datasizeof' using portable standard C++:


Actually it's UB to use 'offsetof' on some types.

I'm going to make a second attempt at implementing '__datasizeof' in
portable standard C++, this time without using 'offsetof' because it
gives dodgy results with some types. (Go to:
https://en.wikipedia.org/wiki/Offsetof and scroll down to
'Limitations').

First and foremost, on implementations of C++ that ignore
'no_unique_address', this implementation of __datasizeof will always
yield the same result as sizeof.

So here's what I'll do. I shall add one byte to the end of T, see if
the struct has expanded in size, then add another byte, then add
another byte, then add another byte, until the size of the containing
struct changes. I will find out how many bytes I need to add until the
struct expands. So let's start off with a helper class:

    template<typename T, std::size_t count>
    class PlusBytes {
        [[no_unique_address]] T obj;
        char c[count];
    };

And so then the implementation of a function to count the bytes of
tail padding would be:

    template<typename T>
    consteval std::size_t tailpadding(void) noexcept
    {
        if ( sizeof(PlusBytes<T,1u>) > sizeof(T) ) return 0u;
        if ( sizeof(PlusBytes<T,2u>) > sizeof(T) ) return 1u;
        if ( sizeof(PlusBytes<T,3u>) > sizeof(T) ) return 2u;
        if ( sizeof(PlusBytes<T,4u>) > sizeof(T) ) return 3u;
        . . .
        . . .
        . . . // and on and on up as far as sizeof(T)-1
    }

That looks pretty sturdy do me. Now we just have to replace that 'if'
ladder with a fold expression. So we need a fold expression that works
on an integer sequence going from 0 to sizeof(T)-2. So let's create
our index sequence:

    template<typename T>
    consteval std::size_t tailpadding(void) noexcept
    {
        using IndexSeq = std::make_index_sequence<sizeof(T) - 1u>; //
e.g. for 16, yields 0...14
        return tailpadding_detail<T>( IndexSeq{} );
    }

Inside the implementation of 'tailpadding_detail', we will have a fold
expression:

    template<typename T, std::size_t... numbers>
    consteval std::size_t
tailpadding_detail(std::index_sequence<numbers...>) noexcept
    {
        if constexpr ( 0u == sizeof...(numbers) ) return 0u;
        else return (... + how_many_bytes<T,numbers>());
    }

And finally we need to write a function called 'how_many_bytes' that
will return 0 when we give it any other number than the amount of
trailing padding bytes:

    template<typename T, std::size_t index_minus_one> // start at 1
instead of 0
    consteval std::size_t how_many_bytes(void) noexcept // thus
0...14 becomes 1...15
    {
        constexpr std::size_t index = index_minus_one + 1u;

        // Check if this one expanded the struct or not
        if ( sizeof(PlusBytes<T,index>) == sizeof(T) ) return 0u;

        // Check if previous one expanded the struct or not
        if ( sizeof(PlusBytes<T,index - 1u>) > sizeof(T) ) return 0u;

        return index - 1u;
    }

Then we would write 'datasizeof' as follows:

    template<typename T>
    consteval std::size_t datasizeof(void) noexcept
    {
        return sizeof(T) - tailpadding<T>();
    }

Here it is all together on GodBolt working with g++, clang++ and Intel icx:

    https://godbolt.org/z/KWb419M4T

This implementation of 'datasizeof' can then be used to write an
'std::optional' class that puts the flag inside tail padding:

    https://godbolt.org/z/aP6xP5zzT

Received on 2023-12-03 17:21:06