-
Notifications
You must be signed in to change notification settings - Fork 3
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
Conversation
Hels15
commented
Aug 12, 2024
- Added basic container
- Basic test cases(not complete, quite lacking)
- Got rid of relative paths
this->change_size(diff); | ||
}; // freestanding-deleted | ||
constexpr void assign(std::initializer_list<T> il) { | ||
if constexpr (Capacity == 0) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
On Mon, Aug 19, 2024, 11:16 Jeff Garland ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In Include/Beman/InplaceVector/InplaceVector.hpp
<#1 (comment)>
:
> + return;
+ }
+ 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());
+ std::destroy(new_end, this->end);
+ } else {
+ std::copy(il.begin(), il.begin() + this->size(), this->begin());
+ base::uninitialized_copy(il.begin() + this->size(), il.end(),
+ this->end());
+ }
+ this->change_size(diff);
+ }; // freestanding-deleted
+
+ // [containers.sequences.inplace.vector.access], element access
+ constexpr reference at(const size_type count) {
https://eel.is/c++draft/inplace.vector.overview standard doesn't have
const in the signature -- that might fall into implementer freedom though...
const on size_t as a parameter is a no-op though? It does mean that the
implementation can't modify their copy of the parameter, but the caller
doesn't care. (Except on old Solaris compilers that mangled things
incorrectly.)
… constexpr reference at(size_type n); // freestanding-deleted
constexpr const_reference at(size_type n) const; // freestanding-deleted
—
Reply to this email directly, view it on GitHub
<#1 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAVNZ5TD3P43LV5SGFA6F5TZSID5BAVCNFSM6AAAAABMMAY5G6VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDENBVHAZDSOJZGM>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
@@ -1,3 +1 @@ | |||
int main() { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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> |
There was a problem hiding this comment.
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" | ||
) | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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<>
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep
@@ -0,0 +1,750 @@ | |||
// A ContiguousContainer is a Container that stores objects in contiguous memory | |||
// locations. | |||
|
There was a problem hiding this comment.
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>
.
There was a problem hiding this comment.
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. | |||
|
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#else |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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" | ||
) | ||
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
Co-authored-by: David Sankel <[email protected]>
I will need some time to resolve all these issues, thank you everyone for reviewing it. |
`inplace_vector_size_type` to `inplace_vector_internal_size_type`.
# Conflicts: # Include/Beman/InplaceVector/inplace_vector.hpp
@steve-downey @camio @JeffGarland @inbal2l, Please provide me a new review/resolve them if I fixed the problem in the latest commit. |
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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
I've finished pass 2. This is looking pretty good - thx @Hels15! |
What is the current state of this PR now? @JeffGarland @steve-downey |
using size_type = std::size_t; | ||
using internal_size_type = inplace_vector_internal_size_type<Capacity>; | ||
|
||
alignas(T) unsigned char elems[Capacity * sizeof(T)] = {}; |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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);
}
std::byte is the current model for the smallest addressable piece of
memory, so that makes sense, I suppose. It doesn't have all of the integral
things that a char has, but still allows aliasing, by fiat.
…On Mon, Sep 16, 2024, 17:42 Jeff Garland ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In Include/Beman/InplaceVector/inplace_vector.hpp
<#1 (comment)>
:
> +// The internal size type can be smaller than std::size_t when capacity allows
+// for it.
+template <std::size_t Capacity>
+using inplace_vector_internal_size_type =
+ If<Capacity <= std::numeric_limits<uint8_t>::max(), uint8_t,
+ If<Capacity <= std::numeric_limits<uint16_t>::max(), uint16_t,
+ If<Capacity <= std::numeric_limits<uint32_t>::max(), uint32_t,
+ uint64_t>>>;
+
+// Base class for inplace_vector
+template <typename T, std::size_t Capacity>
+struct inplace_vector_destruct_base {
+ using size_type = std::size_t;
+ using internal_size_type = inplace_vector_internal_size_type<Capacity>;
+
+ alignas(T) unsigned char elems[Capacity * sizeof(T)] = {};
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
—
Reply to this email directly, view it on GitHub
<#1 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAVNZ5RDVJCGRE7H3AWJW4TZW5GGBAVCNFSM6AAAAABMMAY5G6VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDGMBXHA4TOMZWGE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
emplace_back(*first); | ||
} | ||
}; // freestanding-deleted | ||
constexpr void pop_back() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.