Skip to content

Commit cd047be

Browse files
committed
feat: implementation of gsl::dyn_array
Implement gsl::dyn_array<T, Allocator> as specified by the CppCoreGuidlines here: https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/dyn_array.md Currently the implementation is feature complete and all tests are passing under (at least) the following presets: - clang-14-debug - clang-17-debug - clang-20-debug - clang-23-debug (clang 18.1.3 on Ubuntu 24.04) (Apple Clang 17.0.0 on macOS Tahoe 26.3) - msvc-14-debug - msvc-17-debug - msvc-20-debug - msvc-23-debug (Visual Studio 2026 18.2 (toolset v145)) What is done: - [x] Basic Functionality - [x] Tests of basic functionality - [x] Tests for constexpr functions - [x] Tests/Support for ranges - [x] Ensure support for C++14, C++17, C++20, and C++23 - [x] Tests for custom allocators - [x] More constexpr tests using a constexpr allocator - [x] Tests for const iterators - [x] Sanity-check static_assertions to make sure gsl::dyn_array::iterator is a proper random-access iterator. - [x] Sanity-check static_assertions to make sure gsl::dyn_array is (mostly) a proper allocator-aware container - [x] Tests for construction of gsl::dyn_array from std::initializer_list - [x] Tests/Support for operations on const gsl::dyn_array - [x] Support for MSVC's unchecked iterators - [x] Tests/Support for reverse iterators - [x] Deduction guides for gsl::dyn_array What needs to be done: - [ ] Run dyn_array tests under ASAN - [ ] Ensure support for clang, gcc, MSVC - [ ] Create docs
1 parent 756c91a commit cd047be

File tree

9 files changed

+714
-13
lines changed

9 files changed

+714
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ span_p | &#x26
4141
[cu32zstring](docs/headers.md#user-content-H-zstring) | &#x2611; | An alias to `basic_zstring` with dynamic extent and a char type of `const char32_t`
4242
[**2. Owners**][cg-owners] | |
4343
stack_array | &#x2610; | A stack-allocated array
44-
dyn_array | &#x2610; | A heap-allocated array
44+
dyn_array | &#x2611; | A heap-allocated array
4545
[**3. Assertions**][cg-assertions] | |
4646
[Expects](docs/headers.md#user-content-H-assert-expects) | &#x2611; | A precondition assertion; on failure it terminates
4747
[Ensures](docs/headers.md#user-content-H-assert-ensures) | &#x2611; | A postcondition assertion; on failure it terminates

docs/headers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ See [GSL: Guidelines support library](https://isocpp.github.io/CppCoreGuidelines
99
- [`<algorithms>`](#user-content-H-algorithms)
1010
- [`<assert>`](#user-content-H-assert)
1111
- [`<byte>`](#user-content-H-byte)
12+
- [`<dyn_array>`](#user-content-H-dyn_array)
1213
- [`<gsl>`](#user-content-H-gsl)
1314
- [`<narrow>`](#user-content-H-narrow)
1415
- [`<pointers>`](#user-content-H-pointers)
@@ -155,6 +156,10 @@ constexpr byte to_byte() noexcept;
155156

156157
Convert the given value `I` to a `byte`. The template requires `I` to be in the valid range 0..255 for a `gsl::byte`.
157158

159+
## <a name="H-dyn_array" />`<dyn_array>`
160+
161+
# TODO (@carsonradtke)
162+
158163
## <a name="H-gsl" />`<gsl>`
159164

160165
This header is a convenience header that includes all other [GSL headers](#user-content-H).

include/gsl/dyn_array

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
// -*- C++ -*-
2+
///////////////////////////////////////////////////////////////////////////////
3+
//
4+
// Copyright (c) 2026 Microsoft Corporation. All rights reserved.
5+
//
6+
// This code is licensed under the MIT License (MIT).
7+
//
8+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
12+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
13+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
14+
// THE SOFTWARE.
15+
//
16+
///////////////////////////////////////////////////////////////////////////////
17+
18+
#ifndef GSL_DYN_ARRAY_H
19+
#define GSL_DYN_ARRAY_H
20+
21+
#include "./assert"
22+
#include "./narrow"
23+
#include "./util"
24+
25+
#include <iterator>
26+
#include <memory>
27+
28+
#ifdef GSL_HAS_RANGES
29+
#include <ranges>
30+
#endif /* GSL_HAS_RANGES */
31+
32+
namespace gsl
33+
{
34+
namespace details
35+
{
36+
template <typename T>
37+
struct dyn_array_traits
38+
{
39+
using value_type = T;
40+
using pointer = T*;
41+
using reference = T&;
42+
using const_reference = const T&;
43+
using difference_type = std::ptrdiff_t;
44+
using size_type = decltype(sizeof(0));
45+
};
46+
47+
template <typename T, typename Allocator = std::allocator<T>>
48+
class dyn_array_base : public Allocator
49+
{
50+
using pointer = typename dyn_array_traits<T>::pointer;
51+
using size_type = typename dyn_array_traits<T>::size_type;
52+
53+
const class dyn_array_impl
54+
{
55+
public:
56+
constexpr dyn_array_impl(pointer data, size_type count) : _data{data}, _count{count}
57+
{
58+
Ensures((_count == 0 && _data == nullptr) || (_count > 0 && _data != nullptr));
59+
}
60+
61+
constexpr auto data() const { return _data; }
62+
63+
constexpr auto count() const { return _count; }
64+
65+
private:
66+
pointer _data;
67+
size_type _count;
68+
} _impl;
69+
70+
public:
71+
constexpr dyn_array_base(const Allocator& alloc) : Allocator{alloc}, _impl{nullptr, 0} {}
72+
constexpr dyn_array_base(size_type count, const Allocator& alloc)
73+
: Allocator{alloc}, _impl{count == 0 ? nullptr : Allocator::allocate(count), count}
74+
{}
75+
76+
GSL_CONSTEXPR_SINCE_CPP20 ~dyn_array_base()
77+
{
78+
if (impl().data()) Allocator::deallocate(impl().data(), impl().count());
79+
}
80+
81+
constexpr auto impl() const -> const dyn_array_impl& { return _impl; }
82+
};
83+
84+
template <typename T>
85+
class dyn_array_iterator
86+
{
87+
using size_type = typename dyn_array_traits<T>::size_type;
88+
89+
public:
90+
using difference_type = typename dyn_array_traits<T>::difference_type;
91+
using value_type = typename dyn_array_traits<T>::value_type;
92+
using pointer = typename dyn_array_traits<T>::pointer;
93+
using reference = typename dyn_array_traits<T>::reference;
94+
using const_reference = typename dyn_array_traits<T>::const_reference;
95+
using iterator_category = std::random_access_iterator_tag;
96+
97+
#ifdef GSL_HAS_RANGES
98+
constexpr dyn_array_iterator() : dyn_array_iterator(nullptr, 0, 0) {}
99+
#endif /* GSL_HAS_RANGES */
100+
101+
constexpr dyn_array_iterator(pointer ptr, size_type pos, size_type end_pos)
102+
: _ptr{ptr}, _pos{pos}, _end_pos{end_pos}
103+
{
104+
Ensures((_ptr != nullptr && _end_pos > 0) || (_ptr == nullptr && _end_pos == 0));
105+
Ensures(_pos <= _end_pos);
106+
}
107+
108+
#if defined(_MSC_VER) && defined(GSL_HAS_RANGES)
109+
// TODO (@carsonradtke): Investigate why this is necessary for MSVC.
110+
// Is it a a bug in GSL? STL? MSVC?
111+
constexpr operator pointer() const { return _ptr + gsl::narrow<size_type>(_pos); }
112+
#endif /* defined(_MSC_VER) && defined(GSL_HAS_RANGES) */
113+
114+
constexpr auto operator==(const dyn_array_iterator& other) const
115+
{
116+
Expects(_ptr == other._ptr);
117+
Expects(_end_pos == other._end_pos);
118+
return _pos == other._pos;
119+
}
120+
121+
constexpr auto operator!=(const dyn_array_iterator& other) const
122+
{
123+
return !(*this == other);
124+
}
125+
126+
constexpr auto operator*() const -> reference
127+
{
128+
Expects(_ptr != nullptr);
129+
Expects(_pos < _end_pos);
130+
return _ptr[_pos];
131+
}
132+
133+
constexpr auto operator++() -> dyn_array_iterator&
134+
{
135+
Expects(_pos < _end_pos);
136+
_pos++;
137+
return *this;
138+
}
139+
140+
constexpr auto operator++(int)
141+
{
142+
auto rv = *this;
143+
++(*this);
144+
return rv;
145+
}
146+
147+
constexpr auto operator--() -> dyn_array_iterator&
148+
{
149+
Expects(_pos > 0);
150+
_pos--;
151+
return *this;
152+
}
153+
154+
constexpr auto operator--(int)
155+
{
156+
auto rv = *this;
157+
--(*this);
158+
return rv;
159+
}
160+
161+
constexpr auto operator+=(difference_type diff) -> dyn_array_iterator&
162+
{
163+
auto pos = gsl::narrow<difference_type>(_pos);
164+
Expects(pos + diff <= gsl::narrow<difference_type>(_end_pos));
165+
_pos = gsl::narrow<size_type>(pos + diff);
166+
return *this;
167+
}
168+
169+
constexpr auto operator-=(difference_type diff) -> dyn_array_iterator&
170+
{
171+
auto pos = gsl::narrow<difference_type>(_pos);
172+
Expects(pos >= diff);
173+
_pos = gsl::narrow<size_type>(pos - diff);
174+
return *this;
175+
}
176+
177+
constexpr auto operator+(difference_type diff) const
178+
{
179+
return dyn_array_iterator{_ptr, _pos + gsl::narrow<size_type>(diff), _end_pos};
180+
}
181+
182+
constexpr auto operator-(difference_type diff) const
183+
{
184+
return dyn_array_iterator{_ptr, _pos + gsl::narrow<size_type>(diff), _end_pos};
185+
}
186+
187+
constexpr auto operator-(const dyn_array_iterator& other) const
188+
{
189+
Expects(_ptr == other._ptr);
190+
Expects(_end_pos == other._end_pos);
191+
return gsl::narrow<difference_type>(_pos) - gsl::narrow<difference_type>(other._pos);
192+
}
193+
194+
constexpr auto operator[](size_type pos) -> reference
195+
{
196+
Expects(_pos + pos < _end_pos);
197+
return _ptr[_pos + pos];
198+
}
199+
200+
constexpr auto operator[](size_type pos) const -> const_reference
201+
{
202+
return const_cast<dyn_array_iterator&>(*this).operator[](pos);
203+
}
204+
205+
private:
206+
pointer _ptr;
207+
size_type _pos;
208+
size_type _end_pos;
209+
};
210+
} // namespace details
211+
212+
template <typename T, typename Allocator = std::allocator<T>>
213+
class dyn_array : public details::dyn_array_base<T, Allocator>
214+
{
215+
using base = details::dyn_array_base<T, Allocator>;
216+
using pointer = typename details::dyn_array_traits<T>::pointer;
217+
218+
public:
219+
using value_type = typename details::dyn_array_traits<T>::value_type;
220+
using reference = typename details::dyn_array_traits<T>::reference;
221+
using const_reference = typename details::dyn_array_traits<T>::const_reference;
222+
using iterator = details::dyn_array_iterator<T>;
223+
using const_iterator = details::dyn_array_iterator<const T>;
224+
using reverse_iterator = std::reverse_iterator<iterator>;
225+
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
226+
using difference_type = typename details::dyn_array_traits<T>::difference_type;
227+
using size_type = typename details::dyn_array_traits<T>::size_type;
228+
229+
using allocator_type = Allocator;
230+
231+
explicit constexpr dyn_array(const Allocator& alloc = {}) : base{alloc} {}
232+
233+
explicit constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})
234+
: base{count, alloc}
235+
{
236+
std::fill(begin(), end(), value);
237+
}
238+
239+
GSL_TYPE_IS_ITERATOR(InputIt)
240+
constexpr dyn_array(InputIt first, InputIt last, const Allocator& alloc = {})
241+
: base{gsl::narrow<size_type>(std::distance(first, last)), alloc}
242+
{
243+
std::copy(first, last, begin());
244+
}
245+
246+
#ifdef GSL_HAS_CONTAINER_RANGES
247+
template <typename InputRg>
248+
requires(std::ranges::input_range<InputRg>)
249+
constexpr dyn_array(std::from_range_t, InputRg&& rg, const Allocator& alloc = {})
250+
: base{gsl::narrow<size_type>(std::size(rg)), alloc}
251+
{
252+
std::ranges::copy(rg, std::ranges::begin(*this));
253+
}
254+
#endif /* GSL_HAS_RANGES */
255+
256+
constexpr explicit dyn_array(size_type count, const Allocator& alloc = {})
257+
: dyn_array{count, T{}, alloc}
258+
{}
259+
260+
constexpr dyn_array(const dyn_array& other, const Allocator& alloc = {})
261+
: dyn_array(other.begin(), other.end(), alloc)
262+
{}
263+
264+
constexpr dyn_array(std::initializer_list<T> init, const Allocator& alloc = {})
265+
: dyn_array(init.begin(), init.end(), alloc)
266+
{}
267+
268+
constexpr auto operator=(const dyn_array& other) -> dyn_array& { return dyn_array{other}; }
269+
270+
constexpr dyn_array(dyn_array&&) = delete;
271+
dyn_array& operator=(dyn_array&&) = delete;
272+
273+
constexpr auto operator==(const dyn_array& other) const
274+
{
275+
return size() == other.size() && std::equal(begin(), end(), other.begin(), other.end());
276+
}
277+
278+
constexpr auto operator!=(const dyn_array& other) const { return !(*this == other); }
279+
280+
constexpr auto size() const { return impl().count(); }
281+
282+
constexpr auto empty() const { return size() == 0; }
283+
284+
constexpr auto max_size() const { return static_cast<size_type>(-1); }
285+
286+
constexpr auto get_allocator() -> Allocator& { return *this; }
287+
288+
constexpr auto operator[](size_type pos) -> reference
289+
{
290+
Expects(pos < size());
291+
return data()[pos];
292+
}
293+
294+
constexpr auto operator[](size_type pos) const -> const_reference
295+
{
296+
return const_cast<dyn_array&>(*this)[pos];
297+
}
298+
299+
constexpr auto data() { return impl().data(); }
300+
constexpr auto data() const -> const T* { return const_cast<dyn_array&>(*this).data(); }
301+
302+
constexpr auto begin() { return iterator{data(), 0, size()}; }
303+
constexpr auto begin() const { return const_iterator{data(), 0, size()}; }
304+
305+
constexpr auto rbegin() { return reverse_iterator{end()}; }
306+
constexpr auto rbegin() const { return const_reverse_iterator{end()}; }
307+
308+
#ifdef _MSC_VER
309+
constexpr auto _Unchecked_begin() { return data(); }
310+
constexpr auto _Unchecked_begin() const -> const pointer
311+
{
312+
return const_cast<dyn_array&>(*this)._Unchecked_begin();
313+
}
314+
#endif /* _MSC_VER */
315+
316+
constexpr auto end() { return iterator{data(), size(), size()}; }
317+
constexpr auto end() const { return const_iterator{data(), size(), size()}; }
318+
319+
constexpr auto rend() { return reverse_iterator{begin()}; }
320+
constexpr auto rend() const { return const_reverse_iterator{begin()}; }
321+
322+
#ifdef _MSC_VER
323+
constexpr auto _Unchecked_end() { return data() + size(); }
324+
constexpr auto _Unchecked_end() const -> const pointer
325+
{
326+
return const_cast<dyn_array&>(*this)._Unchecked_end();
327+
}
328+
#endif /* MSC_VER */
329+
330+
private:
331+
constexpr auto impl() const { return base::impl(); }
332+
};
333+
334+
#ifdef GSL_HAS_DEDUCTION_GUIDES
335+
336+
template <class InputIt,
337+
class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
338+
dyn_array(InputIt, InputIt,
339+
Alloc = {}) -> dyn_array<typename std::iterator_traits<InputIt>::value_type, Alloc>;
340+
341+
#ifdef GSL_HAS_CONTAINER_RANGES
342+
template <std::ranges::input_range InputRg,
343+
class Alloc = std::allocator<std::ranges::range_value_t<InputRg>>>
344+
dyn_array(std::from_range_t, InputRg&&,
345+
Alloc = {}) -> dyn_array<std::ranges::range_value_t<InputRg>, Alloc>;
346+
#endif /* GSL_HAS_RANGES */
347+
348+
#endif /* GSL_HAS_DEDUCTION_GUIDES */
349+
} // namespace gsl
350+
351+
#endif /* defined(GSL_DYN_ARRAY_H) */

0 commit comments

Comments
 (0)