Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 42 additions & 27 deletions include/beman/inplace_vector/inplace_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@
#include <cstddef> // for size_t
#include <cstdint> // for fixed-width integer types
#include <cstdio> // for assertion diagnostics
#include <cstdlib> // for abort
#include <functional> // for less and equal_to
#include <iterator> // for reverse_iterator and iterator traits
#include <limits> // for numeric_limits
#include <memory> // for destroy
#include <new> // for operator new
#include <ranges>
#include <stdexcept> // for length_error
#include <type_traits> // for aligned_storage and all meta-functions

// Artifact from previous implementation, can be used as hints for optimizer
#define IV_EXPECT(EXPR)

#ifndef BEMAN_IV_THROW_OR_ABORT
#if !defined(__cpp_exceptions) || __cpp_exceptions < 199711L
#include <cstdlib> // for abort
#define BEMAN_IV_THROW_OR_ABORT(x) abort()
#else
#include <stdexcept> // for length_error
#define BEMAN_IV_THROW_OR_ABORT(x) throw x
#endif
#endif

// beman::from_range_t
namespace beman {
struct from_range_t {};
Expand Down Expand Up @@ -260,8 +268,9 @@ struct inplace_vector
static constexpr size_type max_size() noexcept { return N; }
static constexpr size_type capacity() noexcept { return N; }
constexpr void reserve(size_type n) {
if (n > N) [[unlikely]]
throw std::bad_alloc();
if (n > N) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
}
}
constexpr void shrink_to_fit() {}

Expand Down Expand Up @@ -351,8 +360,9 @@ struct inplace_vector
constexpr T &emplace_back(Args &&...args)
requires(std::constructible_from<T, Args...>)
{
if (!try_emplace_back(std::forward<Args>(args)...)) [[unlikely]]
throw std::bad_alloc();
if (!try_emplace_back(std::forward<Args>(args)...)) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
}
return back();
}
constexpr T &push_back(const T &x)
Expand Down Expand Up @@ -395,12 +405,14 @@ struct inplace_vector
requires(std::constructible_from<T, std::ranges::range_reference_t<R>>)
{
if constexpr (std::ranges::sized_range<R>) {
if (size() + std::ranges::size(rg) > capacity()) [[unlikely]]
throw std::bad_alloc();
if (size() + std::ranges::size(rg) > capacity()) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
}
}
for (auto &&e : rg) {
if (size() == capacity()) [[unlikely]]
throw std::bad_alloc();
if (size() == capacity()) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
}
emplace_back(std::forward<decltype(e)>(e));
}
}
Expand Down Expand Up @@ -438,8 +450,9 @@ struct inplace_vector
assert_iterator_in_range(position);
if constexpr (std::random_access_iterator<InputIterator>) {
if (size() + static_cast<size_type>(std::distance(first, last)) >
capacity()) [[unlikely]]
throw std::bad_alloc{};
capacity()) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
}
}
auto b = end();
for (; first != last; ++first)
Expand Down Expand Up @@ -555,9 +568,9 @@ struct inplace_vector
{
if (sz == size())
return;
else if (sz > N) [[unlikely]]
throw std::bad_alloc{};
else if (sz > size())
else if (sz > N) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
} else if (sz > size())
insert(end(), sz - size(), c);
else {
unsafe_destroy(begin() + sz, end());
Expand All @@ -569,25 +582,27 @@ struct inplace_vector
{
if (sz == size())
return;
else if (sz > N) [[unlikely]]
throw std::bad_alloc{};
else if (sz > size())
else if (sz > N) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
} else if (sz > size()) {
while (size() != sz)
emplace_back(T{});
else {
} else {
unsafe_destroy(begin() + sz, end());
unsafe_set_size(sz);
}
}

constexpr reference at(size_type pos) {
if (pos >= size()) [[unlikely]]
throw std::out_of_range("inplace_vector::at");
if (pos >= size()) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::out_of_range("inplace_vector::at"));
}
return details::inplace_vector::index(*this, pos);
}
constexpr const_reference at(size_type pos) const {
if (pos >= size()) [[unlikely]]
throw std::out_of_range("inplace_vector::at");
if (pos >= size()) [[unlikely]] {
BEMAN_IV_THROW_OR_ABORT(std::out_of_range("inplace_vector::at"));
}
return details::inplace_vector::index(*this, pos);
}

Expand All @@ -606,7 +621,7 @@ struct inplace_vector
std::copyable<T>)
{
for (auto &&e : x)
emplace_back(e);
unchecked_emplace_back(e);
}

constexpr inplace_vector(inplace_vector &&x)
Expand All @@ -618,7 +633,7 @@ struct inplace_vector
std::movable<T>)
{
for (auto &&e : x)
emplace_back(std::move(e));
unchecked_emplace_back(std::move(e));
}

constexpr inplace_vector &operator=(const inplace_vector &x)
Expand All @@ -636,7 +651,7 @@ struct inplace_vector
{
clear();
for (auto &&e : x)
emplace_back(e);
unchecked_emplace_back(e);
return *this;
}

Expand All @@ -655,7 +670,7 @@ struct inplace_vector
{
clear();
for (auto &&e : x)
emplace_back(std::move(e));
unchecked_emplace_back(std::move(e));
return *this;
}

Expand Down
11 changes: 11 additions & 0 deletions tests/beman/inplace_vector/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ add_gtest(size_n_data)
add_gtest(erasure)
add_gtest(modifiers)

if(
CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
)
add_gtest(noexceptions)
target_compile_options(
beman.inplace_vector.tests.noexceptions
PRIVATE -fno-exceptions
)
Comment on lines +55 to +58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it ensures that there are no throws in the compiled code. Without it, EXPECT_DEATH could still pass if an unhandled exception was thrown.

Although, I'm now not sure if we need the compiler check. It allows running the tests on compilers that don't support or have a different syntax for disabling exceptions (e.g. MSVC). But right now, CI only includes Clang and GCC, and maybe it would be better to error out instead of silently skipping tests on other compilers. On the other hand, it doesn't matter much, as the implementation should be compiler agnostic as long as there is abort()

        CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
        OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU"

endif()

# constexpr test
add_executable(beman.inplace_vector.tests.constexpr constexpr.test.cpp)
target_link_libraries(
Expand Down
111 changes: 111 additions & 0 deletions tests/beman/inplace_vector/noexceptions.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include <array>

#include "gtest_setup.hpp"

namespace {

template <typename Param> class NoExceptions : public IVBasicTest<Param> {};
TYPED_TEST_SUITE(NoExceptions, IVAllTypes);

TYPED_TEST(NoExceptions, NonThrowing) {

using IV = TestFixture::IV;

const auto reference = this->unique();

IV device;

device.assign(reference.begin(), reference.end());
EXPECT_EQ(device, reference);
device.clear();
}

TYPED_TEST(NoExceptions, IVAllTypes) {

using IV = TestFixture::IV;
using T = TestFixture::T;

IV sanitycheck = this->unique();
EXPECT_EQ(sanitycheck.size(), IV::capacity());

EXPECT_DEATH(
{
IV device = this->unique();
device.emplace_back(T{});
},
".*");

EXPECT_DEATH(
{
IV device = this->unique();
device.resize(IV::capacity() + 1);
},
".*");

EXPECT_DEATH(
{
IV device = this->unique();
device.resize(IV::capacity() + 1, T{});
},
".*");

EXPECT_DEATH(
{
IV device = this->unique();
device.reserve(IV::capacity() + 1);
},
".*");

EXPECT_DEATH(
{
IV device{};
device.append_range(std::array<T, IV::capacity() + 2>{});
},
".*");

EXPECT_DEATH(
{
IV device = this->unique();
device.append_range(std::array<T, 2>{});
},
".*");

// TODO consider adding test for append_range of unsized range

if constexpr (IV::capacity() > 0) {
EXPECT_DEATH(
{
IV device = this->unique();
// test macro fails to compile if we use std::array<T, N> without
// parenthesis ()
auto range = (std::array<T, IV::capacity()>{});
device.insert(device.end(), range.begin(), range.end());
},
".*");
}

EXPECT_DEATH(
{
IV device{};
// test macro fails to compile if we use std::array<T, N> without
// parenthesis ()
auto range = (std::array<T, IV::capacity() + 1>{});
device.insert(device.end(), range.begin(), range.end());
},
".*");

EXPECT_DEATH(
{
IV device = this->unique();
auto e = device.at(IV::capacity() + 1);
},
".*");

EXPECT_DEATH(
{
IV device;
const auto e = device.at(IV::capacity() + 1);
},
".*");
}
} // namespace