Skip to content

Commit

Permalink
Added inplace_any
Browse files Browse the repository at this point in the history
  • Loading branch information
cschreib committed May 19, 2024
1 parent a0ef757 commit a7caf06
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ configure_file("${PROJECT_SOURCE_DIR}/include/snitch/snitch_config.hpp.config"

set(SNITCH_INCLUDES
${PROJECT_SOURCE_DIR}/include/snitch/snitch.hpp
${PROJECT_SOURCE_DIR}/include/snitch/snitch_any.hpp
${PROJECT_SOURCE_DIR}/include/snitch/snitch_append.hpp
${PROJECT_SOURCE_DIR}/include/snitch/snitch_capture.hpp
${PROJECT_SOURCE_DIR}/include/snitch/snitch_cli.hpp
Expand Down
1 change: 1 addition & 0 deletions include/snitch/snitch.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SNITCH_HPP
#define SNITCH_HPP

#include "snitch/snitch_any.hpp"
#include "snitch/snitch_append.hpp"
#include "snitch/snitch_capture.hpp"
#include "snitch/snitch_cli.hpp"
Expand Down
108 changes: 108 additions & 0 deletions include/snitch/snitch_any.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#ifndef SNITCH_ANY_HPP
#define SNITCH_ANY_HPP

#include "snitch/snitch_config.hpp"
#include "snitch/snitch_error_handling.hpp"
#include "snitch/snitch_function.hpp"
#include "snitch/snitch_type_id.hpp"

#include <array>
#include <cstddef>
#include <utility>

namespace snitch {
namespace impl {
template<typename T>
void delete_object(char* storage) noexcept {
reinterpret_cast<T*>(storage)->~T();
}
} // namespace impl

template<std::size_t MaxSize>
class inplace_any {
std::array<char, MaxSize> storage = {};
function_ref<void(char*) noexcept> deleter = [](char*) noexcept {};
type_id_t id = type_id<void>();

void release() noexcept {
deleter = [](char*) noexcept {};
id = type_id<void>();
}

public:
constexpr inplace_any() = default;

inplace_any(const inplace_any&) = delete;

constexpr inplace_any(inplace_any&& other) noexcept :
storage(other.storage), deleter(other.deleter), id(other.id) {
other.release();
}

inplace_any& operator=(const inplace_any&) = delete;

constexpr inplace_any& operator=(inplace_any&& other) noexcept {
reset();
storage = other.storage;
deleter = other.deleter;
id = other.id;
other.release();
return *this;
}

template<typename T, typename... Args>
explicit inplace_any(std::in_place_type_t<T>, Args&&... args) {
emplace<T>(std::forward<Args>(args)...);
}

~inplace_any() {
reset();
}

bool has_value() const noexcept {
return id != type_id<void>();
}

type_id_t type() const noexcept {
return id;
}

template<typename T, typename... Args>
void emplace(Args&&... args) {
static_assert(
sizeof(T) <= MaxSize,
"This type is too large to fit in this inplace_any, increase storage size");

reset();
new (storage.data()) T(std::forward<Args>(args)...);
deleter = &impl::delete_object<T>;
id = type_id<T>();
}

// Requires: not empty and stored type == T.
template<typename T>
const T& get() const {
if (!has_value()) {
assertion_failed("inplace_any is empty");
}
if (type() != type_id<T>()) {
assertion_failed("inplace_any holds an object of a different type");
}

return *reinterpret_cast<const T*>(storage.data());
}

// Requires: not empty and stored type == T.
template<typename T>
T& get() {
return const_cast<T&>(const_cast<const inplace_any*>(this)->get<T>());
}

void reset() noexcept {
deleter(storage.data());
id = type_id<void>();
}
};
} // namespace snitch

#endif
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_project_link_arguments(cpp_arguments, language : 'cpp')
include_dirs = include_directories('.', 'include')

headers = files('include/snitch/snitch.hpp',
'include/snitch/snitch_any.hpp',
'include/snitch/snitch_append.hpp',
'include/snitch/snitch_capture.hpp',
'include/snitch/snitch_cli.hpp',
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(TEST_UTILITY_FILES

set(RUNTIME_TEST_FILES
${TEST_UTILITY_FILES}
${PROJECT_SOURCE_DIR}/tests/runtime_tests/any.cpp
${PROJECT_SOURCE_DIR}/tests/runtime_tests/capture.cpp
${PROJECT_SOURCE_DIR}/tests/runtime_tests/check.cpp
${PROJECT_SOURCE_DIR}/tests/runtime_tests/cli.cpp
Expand Down
143 changes: 143 additions & 0 deletions tests/runtime_tests/any.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "testing.hpp"
#include "testing_assertions.hpp"

namespace {
struct state_monitor {
int* state = nullptr;

state_monitor() = default;

explicit state_monitor(int* s) : state(s) {
*state += 1;
}

~state_monitor() {
*state -= 1;
}
};
} // namespace

TEST_CASE("any", "[utility]") {
constexpr std::size_t max_size = 16;

int state1 = 0;
int state2 = 0;

SECTION("default construct") {
snitch::inplace_any<max_size> storage;
}

SECTION("construct in-place") {
{
snitch::inplace_any<max_size> storage(std::in_place_type_t<state_monitor>{}, &state1);
CHECK(storage.has_value());
CHECK(storage.type() == snitch::type_id<state_monitor>());
CHECK(state1 == 1);
CHECK(storage.get<state_monitor>().state == &state1);
}
CHECK(state1 == 0);
}

SECTION("move constructor") {
{
snitch::inplace_any<max_size> storage1(std::in_place_type_t<state_monitor>{}, &state1);
snitch::inplace_any<max_size> storage2(std::move(storage1));
CHECK(!storage1.has_value());
CHECK(storage2.has_value());
CHECK(state1 == 1);
}
CHECK(state1 == 0);
}

SECTION("move assignment on empty") {
{
snitch::inplace_any<max_size> storage2;
{
snitch::inplace_any<max_size> storage1(
std::in_place_type_t<state_monitor>{}, &state1);
storage2 = std::move(storage1);
CHECK(!storage1.has_value());
}

CHECK(storage2.has_value());
CHECK(state1 == 1);
}
CHECK(state1 == 0);
}

SECTION("move assignment on full") {
{
snitch::inplace_any<max_size> storage2(std::in_place_type_t<state_monitor>{}, &state2);
{
snitch::inplace_any<max_size> storage1(
std::in_place_type_t<state_monitor>{}, &state1);
storage2 = std::move(storage1);
CHECK(!storage1.has_value());
}

CHECK(storage2.has_value());
CHECK(state1 == 1);
CHECK(state2 == 0);
}
CHECK(state1 == 0);
CHECK(state2 == 0);
}

SECTION("emplace and reset") {
{
snitch::inplace_any<max_size> storage;
storage.emplace<state_monitor>(&state1);
CHECK(storage.has_value());
CHECK(storage.type() == snitch::type_id<state_monitor>());
CHECK(state1 == 1);
CHECK(storage.get<state_monitor>().state == &state1);

storage.reset();
CHECK(!storage.has_value());
CHECK(state1 == 0);
}
CHECK(state1 == 0);
CHECK(state2 == 0);
}

SECTION("emplace over existing") {
{
snitch::inplace_any<max_size> storage;
storage.emplace<state_monitor>(&state1);
storage.emplace<state_monitor>(&state2);
CHECK(storage.has_value());
CHECK(storage.type() == snitch::type_id<state_monitor>());
CHECK(state1 == 0);
CHECK(state2 == 1);
CHECK(storage.get<state_monitor>().state == &state2);
}
CHECK(state1 == 0);
CHECK(state2 == 0);
}

SECTION("reset empty") {
snitch::inplace_any<max_size> storage;
storage.reset();
CHECK(!storage.has_value());
}

#if SNITCH_WITH_EXCEPTIONS
SECTION("get empty") {
assertion_exception_enabler enabler;
snitch::inplace_any<max_size> storage;

CHECK_THROWS_WHAT(
storage.get<state_monitor>(), assertion_exception, "inplace_any is empty");
}

SECTION("get wrong type") {
assertion_exception_enabler enabler;
snitch::inplace_any<max_size> storage;
storage.emplace<int>(0);

CHECK_THROWS_WHAT(
storage.get<state_monitor>(), assertion_exception,
"inplace_any holds an object of a different type");
}
#endif
}

0 comments on commit a7caf06

Please sign in to comment.