c++ – Generalization of memcpy

I have to deal with raw memory manipulation. For that I wrote me a function which stores data one after another, and another function which reads this data and stores it into variables.

More precisely:

/**
 * - `void * pack( dest, src1, ..., srcn )`
 *   `void * pack( void * dest, src1, ..., srcn )`
 *    Copies the bytes from values of src1, ..., srcn one after another to the address of dest
 *    The sum of sizeof(srci) must equal sizeof(dest), except, when dest is a void pointer in which no type checking is performed
 *    If an address of vari points to some memory of dest, the behaviour is undefined
 *    Returns a pointer one after the last written byte
 *
 * - `void unpack( src, dest1, ..., destn )`
 *   `void unpack( const void * src, dest1, ..., destn )`
 *    Similar to `pack`, but writes the content of src into the variables dest1, ..., destn
 *    Does not return anything
 *
 * ## Helpers
 *  - ┬┤int64_t sum< ... >()`
 *    function  which returns the sum of all template arguments, arguments must be integers
 */

I want to know

  • Do I invoke undefined behavior anyplace, anywhere, anytime? When so,
    • which tests I could write against it (i.e. which tests are likely to fail at the moment)?
    • what else could I do to get the correct pointer arithmetic here?
  • Is the interface of the functions sensible?
  • Is the __restrict__ qualifier of some use here?
  • Any other comments are also appreciated.
#ifndef CSTRINGUTILS_HPP
#define CSTRINGUTILS_HPP

#include <cassert>
#include <cstring>
#include <cstdint>
#include <type_traits>



/// Macro making `restrict` keyword available
#ifndef restrict
    #if defined(__GNUC__) && ((__GNUC__ == 8) || (__GNUC__ == 9) || (__GNUC__ == 10))
        #define restrict __restrict__
    #else
        #warning "restrict keyword not defined via macro"
    #endif
#endif

namespace detail {

    inline void pack_worker( char * /*dest*/ ) noexcept { }
    template< typename SRC, typename ... TAIL >   inline void pack_worker( char * restrict dest, const SRC * const src, const TAIL * const ... tail ) noexcept {
        static_assert( std::is_trivially_copyable<SRC>::value, "pack cannot handle types which are not trivially copyable" );
        memcpy( dest, src, sizeof(SRC) );
        pack_worker( dest + sizeof(SRC), tail ... );
    }

    inline void unpack_worker( const char * /*src*/ ) noexcept { }
    template< typename DEST, typename ... TAIL >   inline void unpack_worker( const char * restrict src, DEST * dest, TAIL * ... tail ) noexcept {
        static_assert( std::is_trivially_copyable<DEST>::value, "unpack cannot handle types which are not trivially copyable" );
        memcpy( dest, src, sizeof(DEST) );
        unpack_worker( src + sizeof(DEST), tail ... );
    }

    template< int64_t ... > struct sum_worker : std::integral_constant< int64_t, 0 > { };
    template< int64_t X, int64_t ... Xs > struct sum_worker< X, Xs... > : std::integral_constant< int64_t, X + sum_worker<Xs...>::value > { };

}

template< int64_t ... Xs > inline constexpr int64_t sum() { return detail::sum_worker< Xs... >::value; };

template< typename DEST, typename SRC, typename ... TAIL >   inline void * pack( DEST & restrict dest, const SRC & src, const TAIL & ... tail ) noexcept {
    constexpr size_t sze = sum< sizeof(SRC), sizeof(TAIL)... >();
    static_assert( sizeof(DEST) == sze, "Sizes are not compatible. If this is intended, pass `&dest` as `void*`." );
    detail::pack_worker( reinterpret_cast<char *>(&dest), &src, &tail ... );
    return static_cast< void* >( reinterpret_cast<char *>(dest) + sze );
}

template< typename SRC, typename ... TAIL >   inline void * pack( void * restrict dest, const SRC & src, const TAIL & ... tail ) noexcept {
    constexpr size_t sze = sum< sizeof(SRC), sizeof(TAIL)... >();
    detail::pack_worker( reinterpret_cast<char *>(dest), &src, &tail ... );
    return static_cast< void* >( reinterpret_cast<char *>(dest) + sze );
}

template< typename SRC, typename DEST, typename ... TAIL >   inline void unpack( const SRC & restrict src, DEST & dest, TAIL & ... tail ) noexcept {
    constexpr size_t sze = sum< sizeof(DEST), sizeof(TAIL)... >();
    static_assert( sizeof(SRC) == sze, "Sizes are not compatible. If this is intended, pass `&src` as `const void*`." );
    detail::unpack_worker( reinterpret_cast<char const *>(&src), &dest, &tail ... );
}

template< typename DEST, typename ... TAIL >   inline void unpack( const void * restrict const src, DEST & dest, TAIL & ... tail ) noexcept {
    detail::unpack_worker( reinterpret_cast<char const *>(src), &dest, &tail ... );
}

#endif //CSTRINGUTILS_HPP

Here is my set of unit tests

int main() {

    // GeneralTest
    {
        int8_t c1 = 0xAA, c2 = 0xBB, c1r, c2r;
        int16_t dest;
        pack( dest, c1, c2 );

        int16_t res = 0xBBAA;
        assert( memcmp(&dest, &res, sizeof(dest)) == 0 );

        unpack( dest, c1r, c2r );
        assert( memcmp(&c1, &c1r, sizeof(c1)) == 0 );
        assert( memcmp(&c2, &c2r, sizeof(c2)) == 0 );
        assert( memcmp(&dest, &res, sizeof(dest)) == 0 );
    }
    {
        int16_t c1 = 0xAABB, c1r;
        int32_t c2 = 0xCCDD'EEFF, c2r;
        int8_t c3 = 0x11, c3r;
        int8_t c4 = 0x22, c4r;
        int64_t tmp;
        pack( tmp, c1, c2, c3, c4 );

        int64_t res = 0x2211'CCDD'EEFF'AABB;
        assert( memcmp(&tmp, &res, sizeof(tmp)) == 0 );

        unpack( tmp, c1r, c2r, c3r, c4r );
        assert( memcmp(&c1, &c1r, sizeof(c1)) == 0 );
        assert( memcmp(&c2, &c2r, sizeof(c2)) == 0 );
        assert( memcmp(&c3, &c3r, sizeof(c3)) == 0 );
        assert( memcmp(&c4, &c4r, sizeof(c4)) == 0 );
        assert( memcmp(&tmp, &res, sizeof(tmp)) == 0 );
    }


    // TemporaryTest
    {
        int32_t tmp;
        bool t(4);

        pack( tmp, true, true, false, true );
        unpack( tmp, t(3), t(1), t(2), t(0) );  // strange ordering is on purpose
        bool tr = true;
        bool fa = false;
        assert( memcmp(&t(3), &tr, 1) == 0 );
        assert( memcmp(&t(1), &tr, 1) == 0 );
        assert( memcmp(&t(0), &tr, 1) == 0 );
        assert( memcmp(&t(2), &fa, 1) == 0 );
    }
    {
        uint8_t t(8);
        unpack( 0xAABBCCDD, t(0), t(2), t(4), t(6) );  // strange ordering is on purpose
        uint8_t r0 = 0xDD, r2 = 0xCC, r4 = 0xBB, r6 = 0xAA;
        assert( memcmp(&t(0), &r0, 1) == 0 );
        assert( memcmp(&t(2), &r2, 1) == 0 );
        assert( memcmp(&t(4), &r4, 1) == 0 );
        assert( memcmp(&t(6), &r6, 1) == 0 );
    }


    // VoidTest
    {
        char tmp(4);
        pack( (void*)(&tmp), true, false, false );
        bool tr = true;
        bool fa = false;
        assert( memcmp(&tmp(0), &tr, 1) == 0 );
        assert( memcmp(&tmp(1), &fa, 1) == 0 );
        assert( memcmp(&tmp(1), &fa, 1) == 0 );

        bool b1, b2, b3;
        unpack( (const void*)(&tmp), b1, b2, b3 );
        assert( memcmp(&b1, &tr, 1) == 0 );
        assert( memcmp(&b2, &fa, 1) == 0 );
        assert( memcmp(&b3, &fa, 1) == 0 );
    }



    // PackTestSizeTest
    {
        // int8_t c1 = 0xAA, c2 = 0xBB, c3 = 0xCC;
        // int16_t tmp;
        // pack( tmp, c1, c2, c3 );  // Must not compile
        // pack( tmp, c1 );  // Must not compile
    }
    {
        // int tmp;
        // pack( tmp, 10, 20 );  // Must not compile
    }

}
```