Skip to content

Commit

Permalink
Add ForwardDeclaredStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
rHermes committed Jun 8, 2024
1 parent 1c61039 commit dc5d1f8
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 17 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ TODO(rHermes): This is a zip view for doing sorting.
This is a simple assert library. It's made so that I could learn about the preprocessor. The main
need here is to be able to turn it off during release.


#### Forward Declared Storage for Fast pimpl

This class helps with implementing the "Fast Pimpl" idiom. It's currently under development

#### TODO

- Implement better documentation. For this libraries to be useful, I will need to have good documentation.
- Think a bit about what kind of dependencies we will require.
- Figure out how to do cmake install.
- Implement policy types for `ForwardDeclaredStorage`
- Implement all the features from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2584r0.pdf

### Data structures
Expand Down Expand Up @@ -259,4 +265,4 @@ docker buildx build . -t rsentinel/gcc-builder --platform linux/amd64,linux/arm6
### Todo
- Add clang builder image and use that.
- Move away from github actions?
- Not sure if I want this, given that it's free and more testing never hurt.
- Not sure if I want this, given that it's free and more testing never hurt.
25 changes: 10 additions & 15 deletions examples/play.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
#include <future>
#include <iostream>

class C
int
small_comp(const int i)
{
public:
void foo() const& { std::cout << "foo() const&\n"; }
void foo() && { std::cout << "foo() &&\n"; }
void foo() & { std::cout << "foo() &\n"; }
void foo() const&& { std::cout << "foo() const&&\n"; }
};
std::this_thread::sleep_for(std::chrono::seconds(2));
return i * 2;
}

int
main()
{
C x;
x.foo(); // calls foo() &
C{}.foo(); // calls foo() &&
std::move(x).foo(); // calls foo() &&

const C cx;
cx.foo(); // calls foo() const&
std::move(cx).foo(); // calls foo() const&&
std::future<int> theAnswer = std::async(small_comp, 10);
std::cout << "Here we are after starting that task.\n";
std::cout << "And we finished now: " << theAnswer.get() << '\n';
return 0;
}
91 changes: 91 additions & 0 deletions include/hage/core/forward_declared_storage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#pragma once
#include <cstddef>
#include <new>
#include <utility>

namespace hage {

namespace details {

template<std::size_t ExpectedSize, std::size_t ActualSize, std::size_t ExpectedAlignment, std::size_t ActualAlignment>
constexpr inline void
compare_size()
{
static_assert(ExpectedSize == ActualSize, "The size for the ForwardDeclareStorage is wrong");
static_assert(ExpectedAlignment == ActualAlignment, "The alignment for the ForwardDeclareStorage is wrong");
}

} // namespace details

// Inspired by:
// https://probablydance.com/2013/10/05/type-safe-pimpl-implementation-without-overhead/

template<typename T, std::size_t Size, std::size_t Alignment>
class ForwardDeclaredStorage
{
private:
alignas(Alignment) std::byte m_data[Size];

constexpr T& get() { return *std::launder(reinterpret_cast<T*>(&m_data)); }
constexpr const T& get() const { return *std::launder(reinterpret_cast<const T*>(&m_data)); }

public:
constexpr ForwardDeclaredStorage() { ::new (m_data) T; }

constexpr ~ForwardDeclaredStorage()
{
details::compare_size<Size, sizeof(T), Alignment, alignof(T)>();
get().~T();
}

template<typename... Args>
constexpr explicit ForwardDeclaredStorage(std::in_place_t, Args&&... args)
{
::new (m_data) T(std::forward<Args>(args)...);
}

constexpr ForwardDeclaredStorage(ForwardDeclaredStorage&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
{
::new (m_data) T(std::move(other.get()));
}
constexpr ForwardDeclaredStorage(const ForwardDeclaredStorage& other) { ::new (m_data) T(other.get()); }

constexpr ForwardDeclaredStorage(const T& other) { ::new (m_data) T(other); }
constexpr explicit ForwardDeclaredStorage(T&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
{
::new (m_data) T(std::move(other));
}

constexpr ForwardDeclaredStorage& operator=(const ForwardDeclaredStorage& other)
{
get() = other.get();
return *this;
}

constexpr ForwardDeclaredStorage& operator=(const T& other)
{
get() = other;
return *this;
}

constexpr ForwardDeclaredStorage& operator=(T&& other) noexcept(std::is_nothrow_move_assignable_v<T>)
{
get() = std::move(other);
return *this;
}

constexpr ForwardDeclaredStorage& operator=(ForwardDeclaredStorage&& other) noexcept(
std::is_nothrow_move_assignable_v<T>)
{
get() = std::move(other.get());
return *this;
}

constexpr T* operator->() { return &get(); }
constexpr const T* operator->() const { return &get(); };

constexpr T& operator*() { return get(); }
constexpr const T& operator*() const { return get(); }
};

} // namespace hage
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(CORE_HEADER_LIST
"${hage_SOURCE_DIR}/include/hage/core/assert.hpp"
"${hage_SOURCE_DIR}/include/hage/core/charconv.hpp"
"${hage_SOURCE_DIR}/include/hage/core/zip_view.hpp"
"${hage_SOURCE_DIR}/include/hage/core/forward_declared_storage.hpp"
)


Expand Down
5 changes: 4 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ add_executable(hage_test doctest.cpp
atomics_tests.cpp
rotating_file_sink_tests.cpp
core_charconv_tests.cpp
core_traits_tests.cpp)
core_traits_tests.cpp
fast_pimpl_test.cpp
fast_pimpl_test.hpp
forward_declared_storage_tests.cpp)

find_package(Threads REQUIRED)

Expand Down
58 changes: 58 additions & 0 deletions tests/fast_pimpl_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Created by rhermes on 2024-06-08.
//

#include "fast_pimpl_test.hpp"

#include <string>

using namespace hage::test;

namespace hage::test::details {

struct FastPimplTestImpl
{
int a{ 2 };
std::string b{ "this is the default string" };

void setA(int a) { this->a = a; }

int getA() { return this->a; }

void setB(std::string b) { this->b = std::move(b); }
std::string getB() { return this->b; }
};

} // namespace hage::test::details
FastPimplTest::FastPimplTest() = default;
FastPimplTest::~FastPimplTest() = default;
FastPimplTest::FastPimplTest(const FastPimplTest&) = default;
FastPimplTest&
FastPimplTest::operator=(const FastPimplTest&) = default;
FastPimplTest::FastPimplTest(FastPimplTest&&) = default;
FastPimplTest&
FastPimplTest::operator=(FastPimplTest&&) = default;

void
FastPimplTest::setA(int a)
{
m_impl->setA(a);
}

int
FastPimplTest::getA()
{
return m_impl->getA();
}

void
FastPimplTest::setB(std::string b)
{
return m_impl->setB(std::move(b));
}

std::string
FastPimplTest::getB()
{
return m_impl->getB();
}
32 changes: 32 additions & 0 deletions tests/fast_pimpl_test.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <hage/core/forward_declared_storage.hpp>
#include <string>

namespace hage::test {
namespace details {
struct FastPimplTestImpl;
}

class FastPimplTest
{
private:
hage::ForwardDeclaredStorage<details::FastPimplTestImpl, 40, 8> m_impl;

public:
FastPimplTest();
~FastPimplTest();

FastPimplTest(const FastPimplTest&);
FastPimplTest& operator=(const FastPimplTest&);
FastPimplTest(FastPimplTest&&);
FastPimplTest& operator=(FastPimplTest&&);

void setA(int a);
int getA();

void setB(std::string b);
std::string getB();
};

} // namespace hage::test
43 changes: 43 additions & 0 deletions tests/forward_declared_storage_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <doctest/doctest.h>

#include "fast_pimpl_test.hpp"

using namespace hage;

TEST_SUITE_BEGIN("forward_declared_storage");

TEST_CASE("Does move work")
{
test::FastPimplTest hey;

hey.setB("pirate!");
REQUIRE_EQ(hey.getB(), "pirate!");

hey.setA(10);

SUBCASE("Copy assignment")
{
test::FastPimplTest hello;

hello = hey;
REQUIRE_EQ(hey.getB(), "pirate!");
REQUIRE_EQ(hello.getB(), "pirate!");
}

SUBCASE("Move constructor")
{
test::FastPimplTest hello = std::move(hey);
REQUIRE_UNARY(hey.getB().empty());
REQUIRE_EQ(hello.getB(), "pirate!");
}

SUBCASE("Move assignment")
{
test::FastPimplTest hello;
hello = std::move(hey);
REQUIRE_UNARY(hey.getB().empty());
REQUIRE_EQ(hello.getB(), "pirate!");
}
}

TEST_SUITE_END();

0 comments on commit dc5d1f8

Please sign in to comment.