Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ jobs:
args: "-DCMAKE_CXX_FLAGS=-fsanitize=thread"
- description: "ASan"
args: "-DCMAKE_CXX_FLAGS='-fsanitize=address -fsanitize=undefined'"
- description: "NoExcep"
args: "-DBEMAN_INPLACE_VECTOR_NO_EXCEPTIONS=on"
include:
- platform: ubuntu-24.04
compiler:
Expand Down
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ option(
${PROJECT_IS_TOP_LEVEL}
)

option(
BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS
"Disable exceptions by replacing throw calls with abort. Default: OFF. Values: { ON, OFF }."
OFF
)

configure_file(
"${PROJECT_SOURCE_DIR}/include/beman/inplace_vector/config.hpp.in"
"${PROJECT_BINARY_DIR}/include/beman/inplace_vector/config.hpp"
@ONLY
)

include(FetchContent)
include(GNUInstallDirs)

Expand All @@ -38,6 +50,7 @@ target_include_directories(
beman.inplace_vector
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
$<INSTALL_INTERFACE:include>
)

Expand Down
6 changes: 6 additions & 0 deletions include/beman/inplace_vector/config.hpp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef BEMAN_INPLACE_VECTOR_CONFIG_HPP
#define BEMAN_INPLACE_VECTOR_CONFIG_HPP

#cmakedefine01 BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS()

#endif
73 changes: 46 additions & 27 deletions include/beman/inplace_vector/inplace_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,38 @@
#ifndef BEMAN_INPLACE_VECTOR_INPLACE_VECTOR_HPP
#define BEMAN_INPLACE_VECTOR_INPLACE_VECTOR_HPP

#if !defined(__has_include) || __has_include(<beman/inplace_vector/config.hpp>)
#include <beman/inplace_vector/config.hpp>
#endif

#include <algorithm> // for rotate...
#include <array>
#include <compare>
#include <concepts> // for lots...
#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 BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS()
#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 +272,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 +364,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 +409,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 +454,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 +572,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 +586,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 +625,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 +637,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 +655,7 @@ struct inplace_vector
{
clear();
for (auto &&e : x)
emplace_back(e);
unchecked_emplace_back(e);
return *this;
}

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

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

# only add noexception tests if NO_EXCEPTIONS option is set and compiler supports -fno-exceptions
if(
BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS
AND (
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
4 changes: 2 additions & 2 deletions tests/beman/inplace_vector/constructors.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TYPED_TEST(Constructors, SizedDefault) {

EXPECT_EQ(IV(0), IV{});

EXPECT_THROW(IV(IV::capacity() + 1), std::bad_alloc);
SAFE_EXPECT_THROW(IV(IV::capacity() + 1), std::bad_alloc);

constexpr auto mid_size = std::midpoint(0ul, IV::capacity());
IV mid(mid_size);
Expand Down Expand Up @@ -57,7 +57,7 @@ TYPED_TEST(Constructors, SizedValue) {
IV device(0, value);
EXPECT_EQ(device, IV{});

EXPECT_THROW(IV(IV::capacity() + 1, value), std::bad_alloc);
SAFE_EXPECT_THROW(IV(IV::capacity() + 1, value), std::bad_alloc);
}

if constexpr (IV::capacity() < 1u)
Expand Down
17 changes: 9 additions & 8 deletions tests/beman/inplace_vector/container_requirements.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ TYPED_TEST(SequenceContainerRequirements, ConstructorInitializerList) {
using T = TestFixture::T;

if (IV::capacity() == 0) {
EXPECT_THROW(IV({T{20}}), std::bad_alloc);
SAFE_EXPECT_THROW(IV({T{20}}), std::bad_alloc);
return;
}

Expand Down Expand Up @@ -657,7 +657,7 @@ TYPED_TEST(SequenceContainerRequirements, AssignInitializerList) {

if (IV::capacity() == 0) {
IV device;
EXPECT_THROW(device = {T{52}}, std::bad_alloc);
SAFE_EXPECT_THROW(device = {T{52}}, std::bad_alloc);
return;
}

Expand Down Expand Up @@ -787,7 +787,7 @@ TYPED_TEST(SequenceContainerRequirements, AssignIterRange) {
EXPECT_EQ(device, correct);

std::array<T, IV::capacity() + 1> ref{};
EXPECT_THROW(device.assign(ref.begin(), ref.end()), std::bad_alloc);
SAFE_EXPECT_THROW(device.assign(ref.begin(), ref.end()), std::bad_alloc);
}

{
Expand All @@ -802,7 +802,7 @@ TYPED_TEST(SequenceContainerRequirements, AssignIterRange) {
// [containers.sequences.inplace.vector.overview]
// 5. Any member function of inplace_vector<T, N> that would cause the size
// to exceed N throws an exception of type bad_alloc.
EXPECT_THROW(
SAFE_EXPECT_THROW(
device.assign(InputIterator{0}, InputIterator{IV::max_size() + 1}),
std::bad_alloc);
}
Expand Down Expand Up @@ -833,7 +833,7 @@ TYPED_TEST(SequenceContainerRequirements, AssignRange) {
std::array<T, IV::capacity() + 1> ref;
std::copy(correct.begin(), correct.end(), ref.begin());
ref.back() = T{5};
EXPECT_THROW(device.assign_range(ref), std::bad_alloc);
SAFE_EXPECT_THROW(device.assign_range(ref), std::bad_alloc);
}

// a.assign(il)
Expand All @@ -846,7 +846,7 @@ TYPED_TEST(SequenceContainerRequirements, AssignFuncInitializerList) {
auto device = this->unique();

if (device.capacity() == 0) {
EXPECT_THROW(device.assign({T{50}}), std::bad_alloc);
SAFE_EXPECT_THROW(device.assign({T{50}}), std::bad_alloc);
return;
}

Expand Down Expand Up @@ -916,7 +916,8 @@ TYPED_TEST(SequenceContainerRequirements, AssignMulti) {
}

device.clear();
EXPECT_THROW(device.assign(device.capacity() + 1, T{12}), std::bad_alloc);
SAFE_EXPECT_THROW(device.assign(device.capacity() + 1, T{12}),
std::bad_alloc);
}

// a.front()
Expand Down Expand Up @@ -1017,7 +1018,7 @@ TYPED_TEST(SequenceContainerRequirements, ElementAccessAt) {
EXPECT_EQ(device.at(i), *(device.begin() + i));
}

EXPECT_THROW(device.at(IV::capacity()), std::out_of_range);
SAFE_EXPECT_THROW(device.at(IV::capacity()), std::out_of_range);
}

}; // namespace
8 changes: 8 additions & 0 deletions tests/beman/inplace_vector/gtest_setup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,11 @@ template <typename Param> class IVBasicTest : public ::testing::Test {

template <typename Param>
std::size_t IVBasicTest<Param>::InputIterator::num_deref;

#if BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS()
#define SAFE_EXPECT_THROW(x, y) \
do { \
} while (0)
#else
#define SAFE_EXPECT_THROW(x, y) EXPECT_THROW(x, y)
#endif
8 changes: 8 additions & 0 deletions tests/beman/inplace_vector/inplace_vector.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ template <typename T> constexpr void test() {
assert(const_data == std::addressof(const_front));
}

#if BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS()
int main() {
test<int>();
return 0;
}
#else
void test_exceptions() {
using vec = inplace_vector<int, 42>;
{
Expand All @@ -86,8 +92,10 @@ void test_exceptions() {
}
}
}

int main() {
test<int>();
test_exceptions();
return 0;
}
#endif
Loading