-
Notifications
You must be signed in to change notification settings - Fork 11
Review implementation #46
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
Conversation
|
Status of tests coverage:
|
|
Known faults:
|
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
| // TODO: This type is also trivially destructible | ||
| // ~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.
Question: Is it possible to construct a trivially copy constructable struct that is not trivially destructable ?
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.
Answer: no,
is_trivially_copy_constructible is the same as is_trivially_constructible<T, const T&>, which in turn is requires is_constructible<T, const T&> where all operations are known to be trivial. is_constructible is defined in https://eel.is/c++draft/type.traits#meta.unary.prop-9, which refers to a variable, so the destructor is included.
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.
| // TODO: This type is also trivially destructible | |
| // ~T() {} | |
| // trivially copy constructible implies trivially destructible |
| ASSERT_EQ(Counting::num_objects, v.size() + t.size()); | ||
| t = rv(); | ||
| EXPECT_TRUE((std::is_same_v<decltype(t = rv()), X &>)); | ||
| EXPECT_EQ(Counting::num_objects, 2 * v.size()); // t[4] has been destroyed |
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.
Test fails because inplace_vector's destructor is not properly implemented.
| ASSERT_EQ(Counting::num_objects, 5); | ||
| a.~X(); | ||
| EXPECT_TRUE(std::is_void_v<decltype(a.~X())>); | ||
| EXPECT_EQ(Counting::num_objects, 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.
Test fails because inplace_vector's destructor is not properly implemented.
|
Desturctor chain for faulty test (inplace_vector<Counter, Capacity>) Base class: Data Member: destructor is not called for elements in type erased storage. Edit: I think adding this destruct overload to __non_trivial should be sufficient: No need to add this to the other base class due to their trivial nature. CC @jbab |
| // 3 For any N, inplace_vector<T, N>::iterator and inplace_vector<T, | ||
| // N>::const_iterator meet the constexpr iterator requirements. | ||
| // | ||
| // TODO: Test this. |
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.
There are variations of constexpr requirement, and this depends on to be deprecated trivial type trait. We should split this out into individual files.
Specifically:
- <= C++26 constexpr when is_trivial
- >C++26 constexpr for wider T using trivial union, omit any reference to is_trivial
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 mentioned requirement refers to the iterators of inplace_vector, not inplace_vector itself. The constexpr requirement for the iterators is independent from T and its triviality, there is no change planned in >C++26, afaik.
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.
Ah I commented on the wrong lines, should be:
inplace_vector/tests/beman/inplace_vector/spec.test.cpp
Lines 518 to 521 in 06855a2
| // 4 For any N > 0, if is_trivial_v<T> is false, then no inplace_vector<T, N> | |
| // member functions are usable in constant expressions. | |
| // | |
| // This would be tested in subsequent method tests. |
| using __data_t = | ||
| conditional_t<!is_const_v<__T>, __aligned_storage2<__T, __N>, | ||
| const __aligned_storage2<remove_const_t<__T>, __N>>; | ||
| __data_t __data_{}; // BUGBUG: test SIMD types | ||
| __size_type __size_ = 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.
we could use a union based lifetime delegation here instead of byte array based type erasure for supporting trivial union based constexpr:
union U { U() { } ~U() { } __T storage[__N]; };
U __data_;
__size_type __size_ = 0;
This should cover both non-trivial base case and trivial union based constexpr implementation. I think this also omits the need to use aligned storage as well?
Edit: We will need to try this out, and also be aware of code gen issues mentioned in the paper.
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 there is no point switch to a union based approach before the issues addressed in P3074are fixed. As of now, we would need to add a constructor and destructor to the union (as you write above), therefore the storage (and thus the whole inplace_vector) could no longer be trivially constructible and trivially destructible.
Note that the requirement
If is_trivially_destructible_v is true, then:
IV has a trivial destructor
holds for any T, independent of its triviality (and thus, as of now, storage type).
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 general notes on the tests:
-
Tests should achieve a high (ideally full) coverage of all the branches, this includes not only runtime branches, but also "implementation branches" due to, e.g., different storage types for trivial vs non-trivial types.
-
Ideally, we make the tests independent from the implementation (black box) and only derive the test cases from the specification.
-
The current specification tells us that there must be at least two implementation branches for trivial and non-trivial T, so we should at least test with such types
-
The requirements on special member functions provide more information, for example
If is_trivially_destructible_v is true, then:
IV has a trivial destructorthis means that we need to test (at least) with 1. a trivial type, 2. a non-trivial type which is trivially destructible, 3. a non-trivial type which is not trivially destructible.
Note that this does not only affect the test of the destructor, we don't know which other functions may be affected by these implementation branches if we treat the code under test as a black box. -
Further requirements on other other special member functions make it even more complicated
-
In addition there are some special requirements (and thus an implementation branch) for size 0.
All in all, we need to determine the proper combination of types and then run the full test suite on them.
Once we have that we can fix the implementation and experiment with improvements.
(We might even be able to port back the implementation to C++20, ...)
I have some WIP with this respect which I hope I can commit shortly.
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.
we would need to add a constructor and destructor to the union (as you write above), therefore the storage (and thus the whole inplace_vector) could no longer be trivially constructible and trivially destructible.
You are right, I missed that.
I support backporting to C++ 20, the original aim was C++20 for trivial constexpr, C++17 for basic functionality.
We might need to also test for const T.
…for [container.rev.reqmts]
| // We know from the specification of inplace_vector that there are separate | ||
| // implementation branches for trivial and non-trivial types (due to constexpr | ||
| // support). Furthermore, we have the requirements that the triviality of | ||
| // inplace_vector's SMFs depends on the triviality of the element type 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.
I hope this is not a stupid question.
What is SMF?
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.
Thanks for asking. I'm using this all the time so I didn't even notice it's not obvious.
SMF = special member function, i.e. default constructor, copy constructor, move constructor, destructor, copy assignment, move assignment.
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.
Thx for the clarification 👌
| */ | ||
|
|
||
| // TODO(River): using namespace std below, we need to specify | ||
| // beman::from_range_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.
so the actual check if gcc has this is gcc14 and in c++23 mode. Or perhaps better, the feature macro #ifdef __cpp_lib_containers_ranges see also https://en.cppreference.com/w/cpp/ranges/from_range
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.
Given we have intention to backport this to C++20 and earlier, would it make sense to use a custom beman::from_range here?
| static constexpr void __assert_failure(char const *__file, int __line, | ||
| char const *__msg) { | ||
| if consteval { | ||
| throw __msg; // TODO: std lib implementer, do better 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.
well and even more is that this will be legal in c++26 after Poland https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3068r6.html was voted in
I'd suggest we probably don't need this in c++26 where assert and static assert are upgraded to allow user error messages. Ironically I think that's used directly in some static asserts below. Also, a reminder is source_location is probably a better way to get that than the macro approach
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.
👌 thanks for the input, I am not a huge fan of this static asset infrastructure as well.
I will likely remove them or refactor them with static assert if possible
|
ohno! |
|
oh no I can't change it back :( |
DO NOT MERGE
This is intend to review the reference implementation.
Please use GitHub's PR review feature to review the reference implementation.
I am busy with finals, I will jump in when I have time.
Link to paper: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0843r14.html
Currently in working as section 23. https://eel.is/c++draft/inplace.vector
Would recommend starting at: chapter
Goals:
Non-goals:
NOTE
All edits are on an orphan branch, so I would suggest we minimize the edits to the implementation.
cc @jbab @DeveloperPaul123