Skip to content

Conversation

@20162026
Copy link
Collaborator

@20162026 20162026 commented May 8, 2025

Add a freestanding build option that would remove (= delete) all modifier, element access, and resize member functions marked with freestanding-delete in the standard.

This feature would allow more convenient usage of inplace_vector in a freestanding or no-exception environment (e.g. embedded field) by generating a compile-time warning instead of a runtime throw/abort.

Some of the potentially throwing constructors could be kept for convenience (e.g., initializer_list, size, size+value constructors), while leaving it up to the user to ensure proper constructor usage.

alternative implementation

we could have a freestanding implementation in a different namespace e.g. beman::freestanding::inplace_vector or beman::inplace_vector::freestanding::inplace_vector, this might allow us to avoid using macros, CMake options and any potential ODR violations.

implementation

  • constexpr explicit inplace_vector(size_type n);
  • constexpr inplace_vector(size_type n, const T& value);
  • constexpr inplace_vector(InputIterator first, InputIterator last);
  • constexpr inplace_vector(from_range_t, R&& rg);
  • constexpr inplace_vector(initializer_list il);
  • constexpr inplace_vector& operator=(initializer_list);
  • constexpr void assign(InputIterator first, InputIterator last);
  • constexpr void assign_range(R&& rg);
  • constexpr void assign(size_type n, const T& u);
  • constexpr void assign(initializer_list il);
  • constexpr void resize(size_type sz);
  • constexpr void resize(size_type sz, const T& c);
  • static constexpr void reserve(size_type n);
  • constexpr reference at(size_type n);
  • constexpr const_reference at(size_type n) const;
  • emplace_back(Args&&... args);
  • constexpr reference push_back(const T& x);
  • constexpr reference push_back(T&& x);
  • constexpr void append_range(R&& rg);
  • constexpr iterator emplace(const_iterator position, Args&&... args);
  • constexpr iterator insert(const_iterator position, const T& x);
  • constexpr iterator insert(const_iterator position, T&& x);
  • constexpr iterator insert(const_iterator position, size_type n, T& x);
  • constexpr iterator insert(const_iterator position, InputIterator first, InputIterator last);
  • constexpr iterator insert_range(const_iterator position, R&& rg);
  • constexpr iterator insert(const_iterator position, initializer_list il);

Test disabled:

  • constexpr explicit inplace_vector(size_type n);
  • constexpr inplace_vector(size_type n, const T& value);
  • constexpr inplace_vector(InputIterator first, InputIterator last);
  • constexpr inplace_vector(from_range_t, R&& rg);
  • constexpr inplace_vector(initializer_list il);
  • constexpr inplace_vector& operator=(initializer_list);
  • constexpr void assign(InputIterator first, InputIterator last);
  • constexpr void assign_range(R&& rg);
  • constexpr void assign(size_type n, const T& u);
  • constexpr void assign(initializer_list il);
  • constexpr void resize(size_type sz);
  • constexpr void resize(size_type sz, const T& c); Missing tests for constructors and functions #89
  • static constexpr void reserve(size_type n);
  • constexpr reference at(size_type n);
  • constexpr const_reference at(size_type n) const;
  • emplace_back(Args&&... args);
  • constexpr reference push_back(const T& x);
  • constexpr reference push_back(T&& x);
  • constexpr void append_range(R&& rg);
  • constexpr iterator emplace(const_iterator position, Args&&... args);
  • constexpr iterator insert(const_iterator position, const T& x);
  • constexpr iterator insert(const_iterator position, T&& x);
  • constexpr iterator insert(const_iterator position, size_type n, T& x);
  • constexpr iterator insert(const_iterator position, InputIterator first, InputIterator last);
  • constexpr iterator insert_range(const_iterator position, R&& rg);
  • constexpr iterator insert(const_iterator position, initializer_list il);

Test units cleared:

  • compare.test.cpp
  • constexpr.test.cpp
  • constructors.test.cpp
  • container_requirements.test.cpp
  • erasure.test.cpp
  • freestanding.test.cpp
  • inplace_vector.test.cpp
  • modifiers.test.cpp
  • ref_impl.test.cpp
  • size_n_data.test.cpp
  • triviality.test.cpp

TODO:

  • Add a freestanding example
  • Complete freestanding test
  • CMake should fail with error when both NO_EXCEPT and FREESTANDING is enabled.

Functions out of scope:

  • constexpr inplace_vector(const inplace_vector&);
  • constexpr inplace_vector(inplace_vector&&)
    noexcept(N == 0 || is_nothrow_move_constructible_v);
  • constexpr ~inplace_vector();
  • constexpr inplace_vector& operator=(const inplace_vector& other);
  • constexpr inplace_vector& operator=(inplace_vector&& other)
    noexcept(N == 0 || (is_nothrow_move_assignable_v &&
    is_nothrow_move_constructible_v));
  • constexpr iterator begin() noexcept;
  • constexpr const_iterator begin() const noexcept;
  • constexpr iterator end() noexcept;
  • constexpr const_iterator end() const noexcept;
  • constexpr reverse_iterator rbegin() noexcept;
  • constexpr const_reverse_iterator rbegin() const noexcept;
  • constexpr reverse_iterator rend() noexcept;
  • constexpr const_reverse_iterator rend() const noexcept;
  • constexpr const_iterator cbegin() const noexcept;
  • constexpr const_iterator cend() const noexcept;
  • constexpr const_reverse_iterator crbegin() const noexcept;
  • constexpr const_reverse_iterator crend() const noexcept;
  • constexpr bool empty() const noexcept;
  • constexpr size_type size() const noexcept;
  • static constexpr size_type max_size() noexcept;
  • static constexpr size_type capacity() noexcept;
  • static constexpr void shrink_to_fit() noexcept;
  • constexpr reference operator[](size_type n);
  • constexpr const_reference operator[](size_type n) const;
  • constexpr reference front();
  • constexpr const_reference front() const;
  • constexpr reference back();
  • constexpr const_reference back() const;
  • constexpr T* data() noexcept;
  • constexpr const T* data() const noexcept;
  • constexpr void pop_back();
  • template<class... Args>
    constexpr pointer try_emplace_back(Args&&... args);
  • constexpr pointer try_push_back(const T& x);
  • constexpr pointer try_push_back(T&& x);
  • template<container-compatible-range R>
  • constexpr ranges::borrowed_iterator_t try_append_range(R&& rg);
  • template<class... Args>
    constexpr reference unchecked_emplace_back(Args&&... args);
  • constexpr reference unchecked_push_back(const T& x);
  • constexpr reference unchecked_push_back(T&& x);
  • constexpr iterator erase(const_iterator position);
  • constexpr iterator erase(const_iterator first, const_iterator last);
  • constexpr void swap(inplace_vector& x)
    noexcept(N == 0 || (is_nothrow_swappable_v &&
    is_nothrow_move_constructible_v));
  • constexpr void clear() noexcept;
  • constexpr friend bool operator==(const inplace_vector& x,
    const inplace_vector& y);
  • constexpr friend synth-three-way-result
    operator<=>(const inplace_vector& x, const inplace_vector& y);
  • constexpr friend void swap(inplace_vector& x, inplace_vector& y)
    noexcept(N == 0 || (is_nothrow_swappable_v &&
    is_nothrow_move_constructible_v))
    { x.swap(y); }

@20162026 20162026 requested a review from a team May 8, 2025 22:04
@20162026 20162026 marked this pull request as draft May 8, 2025 22:05
@wusatosi
Copy link
Member

wusatosi commented May 9, 2025

Thank you for your contribution! I will review once the PR is ready :)

@camio
Copy link
Member

camio commented May 9, 2025

"-fno-exceptions" and freestanding are different things. Freestanding implementations are required to support exception handling.

It looks like this patch is attempting to give this library "-fno-exceptions" support, which is not Standard C++ although used in practice. I think the best way to support this is to have an option in the top-level CMakeLists.txt file called BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS which defaults to FALSE. That will set a #define in a config.h file that the library will use to disable exception-throwing functions.

@20162026
Copy link
Collaborator Author

20162026 commented May 9, 2025

option in the top-level CMakeLists.txt file called BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS which defaults to FALSE. That will set a #define in a config.h file that the library will use to disable exception-throwing functions.

I don’t quite like this approach because it would be awkward to use the library if only some of the compilation units have exceptions disabled, while other have them enabled and use throwing functions. Unless there is some cmake feature that I'm not aware of...

We could also replace throw with abort, but from the user's point of view, I would rather have throwing functions deleted and get a compile-time error.

"-fno-exceptions" and freestanding are different things. Freestanding implementations are required to support exception handling.

I know that freestanding does not imply that exceptions are disabled. But would it be wrong to use freestanding-deleted implementation when they are? as it would pretty much disable all of the throwing functions without having to use extra macros

@camio
Copy link
Member

camio commented May 9, 2025

"-fno-exceptions" and freestanding are different things. Freestanding implementations are required to support exception handling.

I know that freestanding does not imply that exceptions are disabled. But would it be wrong to use freestanding-deleted implementation when they are? as it would pretty much disable all of the throwing functions without having to use extra macros

It is a huge jump to assume that because someone uses a "-fno-exceptions" dialect of C++ that they must also want a "freestanding-deleted" version of the standard library. Several major companies disable exceptions who aren't working in the embedded space.

option in the top-level CMakeLists.txt file called BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS which defaults to FALSE. That will set a #define in a config.h file that the library will use to disable exception-throwing functions.

I don’t quite like this approach because it would be awkward to use the library if only some of the compilation units have exceptions disabled, while other have them enabled and use throwing functions. Unless there is some cmake feature that I'm not aware of...

Mixing standard C++ and a "-fno-exceptions" dialect of C++ in the same codebase is going to result in ODR violations eventually. I'm okay with this being a more awkward setup to support.

The [CPP.NO_FLAG_FORKING] rule ensures that your project includes only one version of a library. This is a good practice that guarantees you won't get ODR violations due to people using SFINAE/decltype/concepts on the library's functions.

We could also replace throw with abort, but from the user's point of view, I would rather have throwing functions deleted and get a compile-time error.

I believe replacing throw with abort is what the -fno-exceptions dialect does right now.

I think the CMake flag would be better called BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED than BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS if we want this at all.

@20162026
Copy link
Collaborator Author

20162026 commented May 9, 2025

Ok, then I will leave this PR for the 'freestanding-delete' implementation only and address the no-exceptions in a different one

@JeffGarland
Copy link
Member

"-fno-exceptions" and freestanding are different things.

That's fair.

Freestanding implementations are required to support exception handling.

I'm less certain on this. It's true that they say they need to support exceptions, but then many of the method exclusions are done because a method might throw. So I think in reality if you're going with freestanding you're also most likely going to have exceptions off. But I don't disagree with separating the two modes.

I think the CMake flag would be better called BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED than BEMAN_INPLACE_VECTOR_NO_EXCEPTIONS if we want this at all.

In my view the flag should BEMAN_INPLACE_VECTOR_FREESTANDING, without deleted. Yes, the net effect is to delete functions -- but FREESTANDING_DELETED implies the inverse of FREESTANDING to me -- which is HOSTED. w.r.t the NO_EXCEPTIONS -- it might be exactly the same as FREESTANDING...

@wusatosi
Copy link
Member

I am for doing BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED. I will review the rest of the code later.

Co-authored-by: Jeff Garland <[email protected]>
@wusatosi
Copy link
Member

wusatosi commented May 10, 2025

Moved to PR description.

20162026 and others added 3 commits May 10, 2025 22:48
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@wusatosi
Copy link
Member

Actually, I will try to merge this PR, and we can move to your separate implementation approach later on.

@20162026
Copy link
Collaborator Author

20162026 commented May 22, 2025

Actually, I will try to merge this PR, and we can move to your separate implementation approach later on.

Current approach is inconvenient and quite limiting. I dont think it is worth merging as we will have to undo all of the
scaffolding around BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED, as it will be gone.

although there were some commits with general improvement unrelated to freestanding that should be cherrypicked

@wusatosi
Copy link
Member

wusatosi commented May 22, 2025

although there were some commits with general improvement unrelated to freestanding that should be cherrypicked

Yeah I don't want to do all these testing adjustments for freestanding again, I can limit the scope of this PR to only adding testing support for freestanding delete.

@wusatosi
Copy link
Member

Actually, I will wait for your PR up and I think we need to re think the testing approach a bit... Current approach is a bit messy and unclear.

@wusatosi
Copy link
Member

Current testing structure is heavily assumed that there's only one implementation of IV, idk how we will need to update our test suite to accommodate two.

@20162026
Copy link
Collaborator Author

20162026 commented May 22, 2025

Current testing structure is heavily assumed that there's only one implementation of IV, idk how we will need to update our test suite to accommodate two.

both implementations will be derived from a single base, so we will only need to test the difference (deleted or alternative functions). I will try to make a preliminary PR until end of the week, rn a bit busy with day job.

@20162026 20162026 mentioned this pull request May 22, 2025
72 tasks
@camio
Copy link
Member

camio commented May 23, 2025

@wusatosi I think there's some confusion on the meaning of freestanding. All hosted C++ compiler implementations are also freestanding implementations. The difference is that a freestanding implementation 1) may (or may not) omit certain headers, and 2) may (or may not) use "= delete" on designated functions.

What this PR is doing is adding a configuration so that this library will use "= delete" on every function that could be deleted with a freestanding implementation. The config.h approach is entirely appropriate here and there's no need to start a compiler in its "freestanding" mode. All that needs to be done is,

  constexpr void reserve(size_type n)
#if BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED()
    = delete;
#else
   {
    if (n > N) [[unlikely]] {
      BEMAN_IV_THROW_OR_ABORT(std::bad_alloc());
    }
  }
#endif

, for all the functions that can be deleted under freestanding (IMO this is better style than trying to implement a more complicated macro to avoid boilerplate). You also need to do the following for your tests:

#if !BEMAN_INPLACE_VECTOR_FREESTANDING_DELETED()
    // Test that uses functions that may be deleted in a freestanding implementation
    // ...
#endif

Yes, GTest may not compile under some freestanding implementations. I don't think that has any bearing on this PR.

@wusatosi
Copy link
Member

@20162026 the testing bench should be ready.

@20162026
Copy link
Collaborator Author

@wusatosi I guess now we need to decide which approach we're going with for freestanding, this one or #90?

@JeffGarland
Copy link
Member

there's no need to start a compiler in its "freestanding" mode.

I'll agree that the freestanding flag can be flipped independent of the compiler mode. However, there's very good reason to run tests with a freestanding option -- specifically that the impl doesn't attempt to access deleted functions. I'd also argue that the cmake should in fact detect the freestanding option on the compile and automatically select the freestanding version of the impl.

@JeffGarland
Copy link
Member

@wusatosi I guess now we need to decide which approach we're going with for freestanding, this one or #90?

My reading of the thread here is the @20162026 wants to move to #90

@20162026
Copy link
Collaborator Author

I'd also argue that the cmake should in fact detect the freestanding option on the compile and automatically select the freestanding version of the impl

I don't think that freestanding is something we can simply test by enabling some compiler options (let alone detect with CMake), as it's not really a well-defined subset and more of a warning that the function may not exists in freestanding. As I understand it, by deleting all of the freestanding-delete functions, we're just creating a minimal implementation that must work on any platform (hosted or freestanding) that complies with the standard.

IMHO, the best we can do for testing is ensure that the deleted functions are inaccessible, while the others behave exactly as they would with the hosted implementation

My reading of the thread here is the @20162026 wants to move to #90

And yes, I do prefer the alternative approach, as it is more flexible and does not require conditional macros.

@wusatosi
Copy link
Member

I'll agree that the freestanding flag can be flipped independent of the compiler mode. However, there's very good reason to run tests with a freestanding option -- specifically that the impl doesn't attempt to access deleted functions. I'd also argue that the cmake should in fact detect the freestanding option on the compile and automatically select the freestanding version of the impl.

I don't really get what you're suggesting here. But right now this PR structures tests in a non freestanding functions and freestanding only set.

@wusatosi
Copy link
Member

I guess now we need to decide which approach we're going with for freestanding, this one or #90 ?

I think the test bench is setup so that both PR is testable now.

Regarding which implementation to go for, I prefer #90 as well, it is significantly enough that I think having an explicit struct will be more clean.

If we agree on this we can start updating this PR as a testing bench improvement PR, and strip out all the updates to the inplace_vector.

@20162026
Copy link
Collaborator Author

20162026 commented May 27, 2025

If we agree on this we can start updating this PR as a testing bench improvement PR, and strip out all the updates to the inplace_vector.

Personally, I think it would be easier to just make a new PR than try to salvage this one... as there are just too many irrelevant changes. In addition, with the new approach there will be no cmake flag/macro or need for conditional compilation

P.S. is there any Beman consensus regarding code coverage tools and etc?

@wusatosi
Copy link
Member

wusatosi commented May 27, 2025

Personally, I think it would be easier to just make a new PR than try to salvage this one... as there are just too many irrelevant changes. In addition, with the new approach there will be no cmake flag/macro or need for conditional compilation

This makes sense. I can try to open another PR for this.

P.S. is there any Beman consensus regarding code coverage tools and etc?

We have not reached a consensus yet besides using coverall. We can implement it here as another CI job though. We can track this at #82 .


Should we close this PR?

@20162026 20162026 closed this May 27, 2025
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