Skip to content

Instantly share code, notes, and snippets.

@GavinRay97
Created December 9, 2022 19:00
Show Gist options
  • Save GavinRay97/b69cb6ebab6a0e13d2cfe74a6ed7cdc6 to your computer and use it in GitHub Desktop.
Save GavinRay97/b69cb6ebab6a0e13d2cfe74a6ed7cdc6 to your computer and use it in GitHub Desktop.

Revisions

  1. GavinRay97 created this gist Dec 9, 2022.
    118 changes: 118 additions & 0 deletions main.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    // How can we make this program well-defined without sacrificing efficiency? If the destination type
    // is a trivially-copyable implicit-lifetime type, this can be accomplished by copying the storage
    // elsewhere, using placement new of an array of byte-like type, and copying the storage back to its
    // original location, then using std::launder to acquire a pointer to the newly-created object, and
    // finally relying on the compiler to optimise away all the copying. However, this would be very
    // verbose and hard to get right. For expressivity and optimisability, a combined operation to
    // create an object of implicit-lifetime type in-place while preserving the object representation
    // may be useful. This is exactly what std::start_lifetime_as is designed to do

    template<class T>
    T*
    start_lifetime_as(void* p) noexcept
    {
    // Copy the storage to a temporary location, using placement new of an array of byte-like type
    std::byte tmp[sizeof(T)];
    new (tmp) T(*reinterpret_cast<T*>(p));

    // Copy the storage back to its original location
    std::copy(tmp, tmp + sizeof(T), reinterpret_cast<std::byte*>(p));

    // then using std::launder to acquire a pointer to the newly-created object
    // and finally relying on the compiler to optimise away all the copying
    return std::launder(reinterpret_cast<T*>(p));
    }

    // The same operation can be performed on an array of implicit-lifetime type
    template<class T>
    T*
    start_lifetime_as_array(void* p, size_t n) noexcept
    {
    // Copy the storage to a temporary location, using placement new of an array of byte-like type
    std::byte tmp[sizeof(T) * n];
    for (size_t i = 0; i < n; i++)
    {
    new (tmp + i * sizeof(T)) T(reinterpret_cast<T*>(p)[i]);
    }

    // Copy the storage back to its original location
    std::copy(tmp, tmp + sizeof(T) * n, reinterpret_cast<std::byte*>(p));

    // then using std::launder to acquire a pointer to the newly-created object
    // and finally relying on the compiler to optimise away all the copying
    return std::launder(reinterpret_cast<T*>(p));
    }

    template<class T>
    const T*
    start_lifetime_as(const void* p) noexcept
    {
    return start_lifetime_as<T>(const_cast<void*>(p));
    }

    template<class T>
    volatile T*
    start_lifetime_as(volatile void* p) noexcept
    {
    return start_lifetime_as<T>(const_cast<void*>(p));
    }

    template<class T>
    const volatile T*
    start_lifetime_as(const volatile void* p) noexcept
    {
    return start_lifetime_as<T>(const_cast<void*>(p));
    }

    template<class T>
    const T*
    start_lifetime_as_array(const void* p, size_t n) noexcept
    {
    return start_lifetime_as_array<T>(const_cast<void*>(p), n);
    }

    template<class T>
    volatile T*
    start_lifetime_as_array(volatile void* p, size_t n) noexcept
    {
    return start_lifetime_as_array<T>(const_cast<void*>(p), n);
    }

    template<class T>
    const volatile T*
    start_lifetime_as_array(const volatile void* p, size_t n) noexcept
    {
    return start_lifetime_as_array<T>(const_cast<void*>(p), n);
    }

    // Test of the above functions
    int
    main()
    {
    // Create an implicit-lifetime object
    std::array<std::byte, 100> record;
    std::fill(record.begin(), record.end(), std::byte(0x42));

    struct Foo
    {
    int x;
    int y;
    };

    // Create a new object of implicit-lifetime type in-place while preserving the object
    // representation
    Foo* foo = start_lifetime_as<Foo>(record.data());
    printf("%d %d", foo->x, foo->y);

    Foo* foos = start_lifetime_as_array<Foo>(record.data(), record.size() / sizeof(Foo));
    for (size_t i = 0; i < record.size() / sizeof(Foo); i++)
    {
    printf("%d %d", foos[i].x, foos[i].y);
    }

    // Test const version
    const std::array<std::byte, 100> record2 = record;
    const Foo* foo2 = start_lifetime_as<Foo>(record2.data());
    printf("%d %d", foo2->x, foo2->y);

    }