Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Init InplaceVector #1

Merged
merged 13 commits into from
Sep 16, 2024
Merged

Init InplaceVector #1

merged 13 commits into from
Sep 16, 2024

Conversation

Hels15
Copy link
Collaborator

@Hels15 Hels15 commented Aug 12, 2024

  • Added basic container
  • Basic test cases(not complete, quite lacking)

@steve-downey steve-downey self-requested a review August 12, 2024 12:54
 - Got rid of relative paths
this->change_size(diff);
}; // freestanding-deleted
constexpr void assign(std::initializer_list<T> il) {
if constexpr (Capacity == 0) {
Copy link

@JeffGarland JeffGarland Aug 19, 2024

Choose a reason for hiding this comment

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

Is this correct for an empty initializer list? It would prevent the construction of a zero length inplace vector which I think we want to allow?

edit: Also is the correct response actually to throw bad_alloc? because of this: https://eel.is/c++draft/inplace.vector.overview#5

edit2: Since the requirements for initializer list aren't overridden the general sequence container requirements are going to apply - so here: https://eel.is/c++draft/sequence.reqmts#14

Since it's the equivalent of calling with an empty range I think it's a noop. This might be a wording corner case that isn't clear in the standard. I might post on list to get a clarification.

Copy link
Contributor

Choose a reason for hiding this comment

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

I had the same thought as I was reviewing this. Note that this question also applies to the assert in the other assign calls as well.

The correct behavior is to throw std::bad_alloc if the arguments to the assign call have a size greater than the inplace_vector's capacity. This is identical to what would happen if std::vector were assigned to a range sized greater than the available memory on the machine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If I understand it right, the if constexpr (Capacity == 0) assert should be removed from all the assign functions, and should be replaced with std::bad_alloc if the arguments passed in for the assign call have greater size than the capacity? @camio @JeffGarland

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, exactly.

}
const auto diff = static_cast<std::ptrdiff_t>(il.size() - this->size());
if (diff < 0) {
const iterator new_end = std::copy(il.begin(), il.end(), this->begin());

Choose a reason for hiding this comment

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

Since we're in c++20 mode here std::ranges::copy is available.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should all the copies use std.:ranges::copy? I guess so.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it doesn't significantly hurt compilation performance or readability, I don't see why not.

}; // freestanding-deleted

// [containers.sequences.inplace.vector.access], element access
constexpr reference at(const size_type count) {

Choose a reason for hiding this comment

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

https://eel.is/c++draft/inplace.vector.overview standard doesn't have const in the signature -- that might fall into implementer freedom though...

   constexpr reference       at(size_type n);                              // freestanding-deleted
    constexpr const_reference at(size_type n) const;                        // freestanding-deleted

return emplace(position, std::move(x));
}; // freestanding-deleted
constexpr iterator insert(const_iterator position, size_type n, const T &x) {
const iterator pos = (iterator)position;
Copy link

@JeffGarland JeffGarland Aug 19, 2024

Choose a reason for hiding this comment

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

const_cast to be more specific?

edit: actually, is it actually needed at all -- I think it will convert

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, I think both const iterator pos and position have the same type in this case.

} // freestanding-deleted
constexpr iterator insert(const_iterator position,
std::initializer_list<T> il) {
const iterator pos = (iterator)position;
Copy link

@JeffGarland JeffGarland Aug 19, 2024

Choose a reason for hiding this comment

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

See line 679 -- if that one converts why is the cast needed?

@steve-downey
Copy link
Member

steve-downey commented Aug 20, 2024 via email

@@ -1,3 +1 @@
int main() {
Copy link

Choose a reason for hiding this comment

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

Is the case inconsistency with the .hpp file on the filename intentional?

Copy link
Member

@steve-downey steve-downey left a comment

Choose a reason for hiding this comment

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

A few form issues. I'll look at substance soon too.

We still need a testing strategy for catching Mandates (static_assert) errors.

The constify function in Optional26 seems to be useful for constexpr testing, it's identity in a consteval context. Consteval functions that return something testable to EXPECT were good for, for example, testing constexpr emplace for optional.

target_include_directories(
InplaceVector
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../../include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_LOWER_PROJECT_NAME}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../../Include>
Copy link
Member

Choose a reason for hiding this comment

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

I don't particularly like Capitalization in paths, but if that's consensus, OK, I can live with that.

Don't change FHS names. The unnecessary include directory in the source repo is spelled "include" and the files are installed into PREFIX/include.

install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_LOWER_PROJECT_NAME}
FILES_MATCHING
PATTERN "*.hpp")
PATTERN "*.hpp"
)

Copy link
Member

Choose a reason for hiding this comment

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

Globbing is still bad in Cmake, even if it's a little better. List the files.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the best practice is to use FILE_SET as part of the target in CMake so the exact files get installed.

@@ -0,0 +1,88 @@
#include <Beman/InplaceVector/InplaceVector.hpp>
Copy link
Member

Choose a reason for hiding this comment

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

If this is <inplace_vector> it would make sense to spell it that way, at least inplace_vector.hpp, which exports Beman::InplaceVector::inplace_vector<>

Choose a reason for hiding this comment

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

Another thread on this elsewhere. I have to say i dislike the camel case in directory/files very much -- every press of the shift key slows things down.

@@ -0,0 +1,88 @@
#include <Beman/InplaceVector/InplaceVector.hpp>

using namespace beman::InplaceVector;
Copy link
Member

Choose a reason for hiding this comment

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

s/beman/Beman/

Be tediously consistent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not exactly sure what you mean here, where would I put "src/beman/Beman"?

Copy link
Contributor

Choose a reason for hiding this comment

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

@steve-downey is using UNIX hacker jargon. "s/beman/Beman" means "everywhere you see beman replace it with Beman". The origin of this jargon is the UNIX sed command.

Choose a reason for hiding this comment

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

And to be pedantic it should have been s/beman/Beman/g ;)

CMakeLists.txt Outdated
@@ -10,6 +10,7 @@ project(
DESCRIPTION "A dynamically-resizable vector with fixed capacity and embedded storage"
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 20)
Copy link
Contributor

Choose a reason for hiding this comment

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

To support the inclusion of this project within a containing CMake project, the compiler flags should be inherited. Did you require this line to compile?

The stlab library mentions CMAKE_CXX_STANDARD as sometimes necessary for a successful build, but it is used explicitly on the command line.

Copy link
Collaborator Author

@Hels15 Hels15 Aug 21, 2024

Choose a reason for hiding this comment

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

So should I remove it from the cmakelist file and explicitly provide it through the terminal? @camio

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep

Include/Beman/InplaceVector/InplaceVector.hpp Outdated Show resolved Hide resolved
@@ -0,0 +1,750 @@
// A ContiguousContainer is a Container that stores objects in contiguous memory
// locations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Did you consider naming this header inplace_vector.hpp for consistency with the paper which uses #include <inplace_vector>.

Choose a reason for hiding this comment

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

This might be a policy question for Beman as well -- do we want to distinguish from the standard or do we want minimal translation from beman to std

@@ -0,0 +1,750 @@
// A ContiguousContainer is a Container that stores objects in contiguous memory
// locations.

Copy link
Contributor

Choose a reason for hiding this comment

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

See [FILE.LICENSE_ID]. This also applies to other files here.

#include <cassert>
#include <compare>
#include <concepts>
#include <iostream>
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you consider removing this header? It doesn't seem to be used. The same applies to #include <vector> below.

Choose a reason for hiding this comment

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

good point -- iostream is a bad thing to drag in bc of static objects cout, cerr etc. They would prevent use in an embedded context.

emplace_back(std::forward<decltype(value)>(value));
}
}
#else
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#else

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure what you are suggesting here, and GitHub doesn't seem to show much.

this->change_size(diff);
}; // freestanding-deleted
constexpr void assign(std::initializer_list<T> il) {
if constexpr (Capacity == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I had the same thought as I was reviewing this. Note that this question also applies to the assert in the other assign calls as well.

The correct behavior is to throw std::bad_alloc if the arguments to the assign call have a size greater than the inplace_vector's capacity. This is identical to what would happen if std::vector were assigned to a range sized greater than the available memory on the machine.

@@ -2,29 +2,40 @@
# src/Beman/InplaceVector/CMakeLists.txt -*-makefile-*-
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# cmake-format: on

# Add the InplaceVector library
add_library(InplaceVector STATIC inplacevector.cpp)
Copy link
Contributor

Choose a reason for hiding this comment

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

The discussion on what the target name should be is still ongoing. I believe it should at least have the Beman prefix so probably Beman.InplaceVector should be the library name.

install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${CMAKE_LOWER_PROJECT_NAME}
FILES_MATCHING
PATTERN "*.hpp")
PATTERN "*.hpp"
)

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the best practice is to use FILE_SET as part of the target in CMake so the exact files get installed.

@@ -5,7 +5,7 @@

# Tests
add_executable(
InplaceVectorTest.test)
InplaceVectorTest.test InplaceVector.test.cpp)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
InplaceVectorTest.test InplaceVector.test.cpp)
Beman.InplaceVectorTest.test InplaceVector.test.cpp)

All targets need a Beman prefix in case this is incorporated in a larger CMake project. (targets are in a global namespace).

@Hels15
Copy link
Collaborator Author

Hels15 commented Aug 21, 2024

I will need some time to resolve all these issues, thank you everyone for reviewing it.

@Hels15
Copy link
Collaborator Author

Hels15 commented Aug 25, 2024

@steve-downey @camio @JeffGarland @inbal2l, Please provide me a new review/resolve them if I fixed the problem in the latest commit.

@steve-downey
Copy link
Member

Will look

template <typename T>
constexpr bool container_compatible_range =
detail::container_compatible_range_impl<T>;

template <bool Condition, typename TrueType, typename FalseType>
using If = typename std::conditional<Condition, TrueType, FalseType>::type;

// The internal size type can be smaller than std::size_t when capacity allows
// for it.

Choose a reason for hiding this comment

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

clever

@@ -191,13 +192,13 @@ struct inplace_vector_base : public inplace_vector_destruct_base<T, Capacity> {
template <typename Iter>
static constexpr void uninitialized_copy(Iter first, Iter last,
iterator dest) noexcept {

Choose a reason for hiding this comment

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

I think we need conditional noexcept here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So here I need to consider the cases where it is guaranteed that the function doesn't throw any exception. Since it does copying - I think a traditional

noexcept(std::is_nothrow_copy_constructible_v<typename std::iterator_traits<Iter>::value_type>)

should suffice.
@JeffGarland

Choose a reason for hiding this comment

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

That looks correct to me.

@JeffGarland
Copy link

I've finished pass 2. This is looking pretty good - thx @Hels15!

@Hels15
Copy link
Collaborator Author

Hels15 commented Sep 5, 2024

What is the current state of this PR now? @JeffGarland @steve-downey

@camio camio merged commit 69deed7 into beman-project:main Sep 16, 2024
using size_type = std::size_t;
using internal_size_type = inplace_vector_internal_size_type<Capacity>;

alignas(T) unsigned char elems[Capacity * sizeof(T)] = {};

Choose a reason for hiding this comment

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

something I notice on the second pass -- In this paper at least the recommendation is to use std::byte with alignas -- I'm guessing unsigned char is the same, but I'm not alignment expert. I don't want to hold the PR for this, but curious if the better practice now is byte.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf

inplace_vector_base(const inplace_vector_base &other) noexcept(
std::is_nothrow_copy_constructible_v<T>)
: inplace_vector_destruct_base<T, Capacity>(other.size) {
std::ranges::copy(other.begin(), other.end(), begin());

Choose a reason for hiding this comment

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

Does this not work?

std::ranges::copy(other, begin());

// copy other vector into the current vector until it runs ouf of size
std::ranges::copy(other.begin(), other.begin() + size(), begin());
// copy the other half after the end of the current vector
std::ranges::copy(other.begin() + size(), other.end(), end());
Copy link

@JeffGarland JeffGarland Sep 16, 2024

Choose a reason for hiding this comment

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

The 2 copies above need the 2 iterator overload even with ranges::copy since they are partial -- so in this case it makes sense.

constexpr void append_range(R &&rg) {
auto first = rg.begin();
auto last = rg.end();
for (; first != last; ++first) {

Choose a reason for hiding this comment

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

should we consider re-writing with range-for loop. I think that would look something like:

for ( auto& current : rg ) {
    emplace_back(current);
}

@steve-downey
Copy link
Member

steve-downey commented Sep 16, 2024 via email

emplace_back(*first);
}
}; // freestanding-deleted
constexpr void pop_back() {

Choose a reason for hiding this comment

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

I'm also wondering about conditional noexcept here -- it's not noexcept in any form, in the standard text, but certainly could be if T is no throw destructible. Also it's not marked as freestanding deleted -- which I suppose is because no T on freestanding is allowed to throw? Worrying we might have some spec bugs here.

Choose a reason for hiding this comment

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

Re: freestanding
The design in the standard is to avoid originating exceptions. If a user type throws first, then that isn't an issue. This comes to the forefront in std::visit, as it can throw if a variant is in the valueless state. You can only get on the valueless state when an exception is thrown though.

As for the noexceptness, I think it isn't currently noexcept because there's a precondition on the container being non-empty. I thought we generally assumed stuff was noexcept destructible. I think this could be marked unconditionally noexcept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants