From 9e4e86f5404f4e1edde81b9f5059cbb30fe69b10 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Thu, 16 Jan 2025 10:40:00 -0600 Subject: [PATCH 1/6] final draft of proposal --- docs/dyn_array.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/dyn_array.md diff --git a/docs/dyn_array.md b/docs/dyn_array.md new file mode 100644 index 000000000..4adbce899 --- /dev/null +++ b/docs/dyn_array.md @@ -0,0 +1,92 @@ + + +# `gsl::dyn_array` + +### Revision History + +| Date | Author | Description | +| ---- | ------ | ----------- | +| Jan 16, 2025 | Carson Radtke (Microsoft) | Initial draft. | + +A specification to guide the implementation of `gsl::dyn_array`. This is a dynamic array +that is intended to be a replacement for slinging around raw pointers and sizes. +Notably, it _owns_ all of its elements. It can be thought of like a... + +* `std::array` except the size is specified at runtime. +* `std::vector` except it can neither shrink nor grow. + +### Container Named Requirements +In order to allow element access using iterators and to align with various container +idioms, `gsl::dyn_array` should satisfy the following named requirements: + +* Container ([link](https://en.cppreference.com/w/cpp/named_req/Container)) +* ReversibleContainer ([link](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)) +* ContiguousContainer ([link](https://en.cppreference.com/w/cpp/named_req/ContiguousContainer)) +* SequenceContainer ([link](https://en.cppreference.com/w/cpp/named_req/SequenceContainer)) +* AllocatorAwareContainer ([link](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer)) + +### Construction +In addition to the constructors required by the named requirements (default, copy, and +move), `gsl::dyn_array` will support the following constructors: + +* Construct a `dyn_array` with `n` elements, each constructed with `Args...`: +```c++ +template +constexpr explicit dyn_array(size_t n, Args&&...); +template +constexpr dyn_array(size_t n, const Allocator &, Args&&...); +``` + +* Construct a `dyn_array` with elements from the range `[first, last)`: +```c++ +template +constexpr dyn_array(InputIt first, InputIt last) +template +constexpr dyn_array(InputIt first, InputIt last, const Allocator & alloc = Allocator()); +``` + +* Construct a `dyn_array` with elements from the initializer list: +```c++ +constexpr dyn_array(std::initializer_list); +``` + +* Construct a `dyn_array` with elements from the range `R`: +```c++ +template R> constexpr dyn_array(R&&); +``` + +### Operations +In addition to the operations required by the named requirements, `gsl::dyn_array` will +support the following operations: + +* Access the specified element **_with bounds checking_**: +```c++ +constexpr T& operator[](size_t); +constexpr const T& operator[](size_t) const +``` + +* Access the underlying array: +```c++ +constexpr T* data() noexcept; +constexpr const T* data() const noexcept; +``` + +* Return the number of elements in the `dyn_array`: +```c++ +constexpr size_t size() const noexcept; +``` + +#### Note: Why no push_back (and friends)? +`gsl::dyn_array` is intended to be a fixed-size array and all objects should be +constructed at creation. Moreover, the memory overhead of storing another member +variable to track where to push the next item is not desired. + +### Bounds Checking Semantics +If an out-of-bounds access (read or write) is attempted, `gsl::dyn_array` should follow +the contract violation strategy outlined in [GSL.assert: Assertions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#gslassert-assertions). + +### References +* [C++ Named Requirements](https://en.cppreference.com/w/cpp/named_req) +* [Microsoft/GSL #1169](https://github.com/microsoft/GSL/issues/1169) +* [isocpp/CppCoreGuidelines #2244](https://github.com/isocpp/CppCoreGuidelines/issues/2244) +* [n3662](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3662.html) From 41e017c2fef1e06433b7b7841c98b1355818c9f7 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Thu, 16 Jan 2025 11:03:13 -0600 Subject: [PATCH 2/6] fix alloc formal --- docs/dyn_array.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/dyn_array.md b/docs/dyn_array.md index 4adbce899..ce0a483ce 100644 --- a/docs/dyn_array.md +++ b/docs/dyn_array.md @@ -40,19 +40,18 @@ constexpr dyn_array(size_t n, const Allocator &, Args&&...); * Construct a `dyn_array` with elements from the range `[first, last)`: ```c++ template -constexpr dyn_array(InputIt first, InputIt last) -template constexpr dyn_array(InputIt first, InputIt last, const Allocator & alloc = Allocator()); ``` * Construct a `dyn_array` with elements from the initializer list: ```c++ -constexpr dyn_array(std::initializer_list); +constexpr dyn_array(std::initializer_list, const Allocator & alloc = Allocator()); ``` * Construct a `dyn_array` with elements from the range `R`: ```c++ -template R> constexpr dyn_array(R&&); +template R> +constexpr dyn_array(R&&, const Allocator & alloc = Allocator()); ``` ### Operations From dbea5ba08b7ab88738a80aa3715be1a4617b14a2 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Thu, 16 Jan 2025 11:04:51 -0600 Subject: [PATCH 3/6] resolve ambiguity and fix TODO --- docs/dyn_array.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/dyn_array.md b/docs/dyn_array.md index ce0a483ce..ab3ec9fe7 100644 --- a/docs/dyn_array.md +++ b/docs/dyn_array.md @@ -29,13 +29,10 @@ idioms, `gsl::dyn_array` should satisfy the following named requirements: In addition to the constructors required by the named requirements (default, copy, and move), `gsl::dyn_array` will support the following constructors: -* Construct a `dyn_array` with `n` elements, each constructed with `Args...`: +* Construct a `dyn_array` with `n` elements, each copy constructed from `arg`: ```c++ -template -constexpr explicit dyn_array(size_t n, Args&&...); -template -constexpr dyn_array(size_t n, const Allocator &, Args&&...); -``` +constexpr explicit dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator()); +``` * Construct a `dyn_array` with elements from the range `[first, last)`: ```c++ From 1fadfbd3286b92103e122ed14e15d01ca854089c Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Thu, 16 Jan 2025 11:10:34 -0600 Subject: [PATCH 4/6] some wording improvements --- docs/dyn_array.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/dyn_array.md b/docs/dyn_array.md index ab3ec9fe7..248963557 100644 --- a/docs/dyn_array.md +++ b/docs/dyn_array.md @@ -8,16 +8,23 @@ | ---- | ------ | ----------- | | Jan 16, 2025 | Carson Radtke (Microsoft) | Initial draft. | -A specification to guide the implementation of `gsl::dyn_array`. This is a dynamic array -that is intended to be a replacement for slinging around raw pointers and sizes. -Notably, it _owns_ all of its elements. It can be thought of like a... +`gsl::dyn_array` is a dynamic array that is intended to be a replacement for slinging +around raw pointers and sizes. Notably, it _owns_ all of its elements. It should replace +the following idioms: + +* Replace `new T[n]` with `gsl::dyn_array(n)`. +* Replace both `foo(T*, size_t)` and `foo(unique_ptr&, size_t)` with +`foo(gsl::dyn_array&)`. + +It can be thought of like a... * `std::array` except the size is specified at runtime. * `std::vector` except it can neither shrink nor grow. ### Container Named Requirements In order to allow element access using iterators and to align with various container -idioms, `gsl::dyn_array` should satisfy the following named requirements: +idioms for `std::` algorithms, `gsl::dyn_array` should satisfy the following named +requirements: * Container ([link](https://en.cppreference.com/w/cpp/named_req/Container)) * ReversibleContainer ([link](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)) @@ -29,9 +36,14 @@ idioms, `gsl::dyn_array` should satisfy the following named requirements: In addition to the constructors required by the named requirements (default, copy, and move), `gsl::dyn_array` will support the following constructors: +* Construct a `dyn_array` with `n` default constructed elements: +```c++ +constexpr explicit dyn_array(size_t n, const Allocator & alloc = Allocator()); +``` + * Construct a `dyn_array` with `n` elements, each copy constructed from `arg`: ```c++ -constexpr explicit dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator()); +constexpr dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator()); ``` * Construct a `dyn_array` with elements from the range `[first, last)`: From 2ad420c6f1a8677f7b422a678ace3f5d0db2bb40 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Wed, 19 Mar 2025 17:32:24 -0500 Subject: [PATCH 5/6] apply some feedback after offline review w/ @GabrielDosReis --- docs/dyn_array.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/dyn_array.md b/docs/dyn_array.md index 248963557..90e462175 100644 --- a/docs/dyn_array.md +++ b/docs/dyn_array.md @@ -2,12 +2,6 @@ # `gsl::dyn_array` -### Revision History - -| Date | Author | Description | -| ---- | ------ | ----------- | -| Jan 16, 2025 | Carson Radtke (Microsoft) | Initial draft. | - `gsl::dyn_array` is a dynamic array that is intended to be a replacement for slinging around raw pointers and sizes. Notably, it _owns_ all of its elements. It should replace the following idioms: @@ -49,6 +43,7 @@ constexpr dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator( * Construct a `dyn_array` with elements from the range `[first, last)`: ```c++ template + requires (std::input_iterator) constexpr dyn_array(InputIt first, InputIt last, const Allocator & alloc = Allocator()); ``` @@ -59,7 +54,8 @@ constexpr dyn_array(std::initializer_list, const Allocator & alloc = Allocato * Construct a `dyn_array` with elements from the range `R`: ```c++ -template R> +template + requires (std::input_range) constexpr dyn_array(R&&, const Allocator & alloc = Allocator()); ``` From 1ed75689b542b1a4bb519554c8670b47bcf083c9 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Wed, 21 May 2025 16:26:36 -0600 Subject: [PATCH 6/6] Update specification after review from C++ Core Guidelines - Make sure iterators and references cannot be invalidated by any operations (besides destruction). - Update specification to annotate that some functions require certain language features (ranges and concepts). - Remove dependence on "Container" named requirements because they require iterator-invalidating behavior. - Explicitly add default and move constructors. - Fix some typos --- docs/dyn_array.md | 59 +++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/docs/dyn_array.md b/docs/dyn_array.md index 90e462175..2ea87b42d 100644 --- a/docs/dyn_array.md +++ b/docs/dyn_array.md @@ -15,20 +15,22 @@ It can be thought of like a... * `std::array` except the size is specified at runtime. * `std::vector` except it can neither shrink nor grow. -### Container Named Requirements -In order to allow element access using iterators and to align with various container -idioms for `std::` algorithms, `gsl::dyn_array` should satisfy the following named -requirements: - -* Container ([link](https://en.cppreference.com/w/cpp/named_req/Container)) -* ReversibleContainer ([link](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)) -* ContiguousContainer ([link](https://en.cppreference.com/w/cpp/named_req/ContiguousContainer)) -* SequenceContainer ([link](https://en.cppreference.com/w/cpp/named_req/SequenceContainer)) -* AllocatorAwareContainer ([link](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer)) +By design, `gsl::dyn_array` is not a `Container` as defined by the C++ Named +Requirements because we want to avoid the invalidation of iterators or references to +`gsl::dyn_array` objects. ### Construction -In addition to the constructors required by the named requirements (default, copy, and -move), `gsl::dyn_array` will support the following constructors: +`gsl::dyn_array`s can be constructed in the following ways: + +* Default construct a `dyn_array` with no elements: +```c++ +constexpr dyn_array(); +``` + +* Move construct a `dyn_array` from `other`: +```c++ +constexpr dyn_array(dyn_array&& other) noexcept; +``` * Construct a `dyn_array` with `n` default constructed elements: ```c++ @@ -43,20 +45,24 @@ constexpr dyn_array(size_t n, const T& arg, const Allocator & alloc = Allocator( * Construct a `dyn_array` with elements from the range `[first, last)`: ```c++ template - requires (std::input_iterator) +#ifdef __cpp_lib_concepts + requires(std::input_iterator) +#endif /* __cpp_lib_concepts */ constexpr dyn_array(InputIt first, InputIt last, const Allocator & alloc = Allocator()); ``` -* Construct a `dyn_array` with elements from the initializer list: +* Construct a `dyn_array` from a range: ```c++ -constexpr dyn_array(std::initializer_list, const Allocator & alloc = Allocator()); +#ifdef __cpp_lib_containers_range +template + requires(std::ranges::range) +constexpr dyn_array(std::from_range_t, R&& r, const Allocator & alloc = Allocator()); +#endif /* __cpp_lib_containers_range */ ``` -* Construct a `dyn_array` with elements from the range `R`: +* Construct a `dyn_array` with elements from the initializer list: ```c++ -template - requires (std::input_range) -constexpr dyn_array(R&&, const Allocator & alloc = Allocator()); +constexpr dyn_array(std::initializer_list, const Allocator & alloc = Allocator()); ``` ### Operations @@ -66,7 +72,7 @@ support the following operations: * Access the specified element **_with bounds checking_**: ```c++ constexpr T& operator[](size_t); -constexpr const T& operator[](size_t) const +constexpr const T& operator[](size_t) const; ``` * Access the underlying array: @@ -80,10 +86,17 @@ constexpr const T* data() const noexcept; constexpr size_t size() const noexcept; ``` -#### Note: Why no push_back (and friends)? +### FAQ + +#### Why no push_back (and friends)? `gsl::dyn_array` is intended to be a fixed-size array and all objects should be -constructed at creation. Moreover, the memory overhead of storing another member -variable to track where to push the next item is not desired. +constructed at creation. + +#### Why does `gsl::dyn_array` not conform to the `Container` Named Requirements? +`gsl::dyn_array` is intended to be a safer replacement for raw pointers and sizes. We +don't want users to accidentally use it in a way that would be unsafe. For example, +`gsl::dyn_array` does not have copy or move assignment operators. This is because it +would be possible to invalidate existing iterators and references. ### Bounds Checking Semantics If an out-of-bounds access (read or write) is attempted, `gsl::dyn_array` should follow