Skip to content

Conversation

@wusatosi
Copy link
Member

@wusatosi wusatosi commented Dec 6, 2024

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:

  • Check if function signatures are consistent with Paper
  • Check if function attributes are appropriate
  • Check if implementation is valid (e.g. logical error)
  • Check if struct properties are consistent with Paper
  • Propose tests (you are allowed to add tests to review branch, I would not recommend updating the implementation itself).

Non-goals:

  • Large scale refactor
  • Code style recommendation
  • Adopting P3074 (unless comments made to how to leave room for P3074)

NOTE

All edits are on an orphan branch, so I would suggest we minimize the edits to the implementation.

cc @jbab @DeveloperPaul123

@wusatosi
Copy link
Member Author

wusatosi commented Dec 6, 2024

Status of tests coverage:

  • [containers.sequences.inplace.vector.overview] Overview
    • 1 (not testable)
    • 2 container requirement + reversible container requirement + optional sequence container requirements
    • 3 constexpr iterator
    • 4 trivial type leads to constexpr methods
    • 5 throw bad-alloc on size exceed N
    • 6 well formed copy-construable, assignable, move construable, assignable
  • [containers.sequences.inplace.vector.cons] Constructors
  • [containers.sequences.inplace.vector.capacity] Size and capacity
  • [containers.sequences.inplace.vector.data] Data
  • [containers.sequences.inplace.vector.modifiers] Modifiers
  • [containers.sequences.inplace.vector.erasure] Erasure

@wusatosi
Copy link
Member Author

wusatosi commented Dec 6, 2024

Known faults:

  • overview section 6, all trivial copy/move construable requirement.
  • overview section 6, all trivial copy/move assignable requirement.

wusatosi and others added 3 commits December 10, 2024 22:50
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Comment on lines 88 to 89
// TODO: This type is also trivially destructible
// ~T() {}
Copy link
Member Author

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 ?

Copy link
Member Author

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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// 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
Copy link
Collaborator

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);
Copy link
Collaborator

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.

@wusatosi
Copy link
Member Author

wusatosi commented Dec 16, 2024

Desturctor chain for faulty test (inplace_vector<Counter, Capacity>)

// sturct inplace_vector

  //   constexpr ~inplace_vector();
  // from base-class, trivial if is_trivially_destructible_v<__T> &&
  // is_trivially_copy_assignable_v<__T>

Base class:

// sturct __non_trivial

  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;
// ...
  constexpr ~__non_trivial() = default;

Data Member:

template <class __T, size_t __N> struct __aligned_storage2 {
  alignas(__T) byte __d[sizeof(__T) * __N];
  constexpr __T *__data(size_t __i) noexcept {
    __IV_EXPECT(__i < __N);
    return reinterpret_cast<__T *>(__d) + __i;
  }
  constexpr const __T *__data(size_t __i) const noexcept {
    __IV_EXPECT(__i < __N);
    return reinterpret_cast<const __T *>(__d) + __i;
  }
};

destructor is not called for elements in type erased storage.

Edit: I think adding this destruct overload to __non_trivial should be sufficient:

// struct __non_trivial
  constexpr ~__non_trivial() requires(is_trivially_destructible_v<__T>) = default;
  constexpr ~__non_trivial() {
      destroy(__data(), __data() + __size();
  }

No need to add this to the other base class due to their trivial nature.

CC @jbab

Comment on lines 513 to 516
// 3 For any N, inplace_vector<T, N>::iterator and inplace_vector<T,
// N>::const_iterator meet the constexpr iterator requirements.
//
// TODO: Test this.
Copy link
Member Author

@wusatosi wusatosi Dec 16, 2024

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

Copy link
Collaborator

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.

Copy link
Member Author

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:

// 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.

Comment on lines 440 to 444
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;
Copy link
Member Author

@wusatosi wusatosi Dec 17, 2024

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.

Copy link
Collaborator

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).

Copy link
Collaborator

@jbab jbab Dec 17, 2024

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 destructor

    this 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.

Copy link
Member Author

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.

// 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:
Copy link
Member Author

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?

Copy link
Collaborator

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.

Copy link
Member Author

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
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

@JeffGarland JeffGarland Dec 20, 2024

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

Copy link
Member Author

@wusatosi wusatosi Dec 21, 2024

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

@wusatosi wusatosi changed the base branch from review-dummy to main January 14, 2025 21:26
@wusatosi wusatosi closed this Jan 14, 2025
@wusatosi
Copy link
Member Author

ohno!

@wusatosi
Copy link
Member Author

oh no I can't change it back :(

@wusatosi wusatosi deleted the review branch January 30, 2025 22:43
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.

4 participants