Skip to content

Commit 0c59648

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 0c59648

File tree

9 files changed

+715
-13
lines changed

9 files changed

+715
-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: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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+
#ifdef _MSC_VER
109+
// TODO (@carsonradtke): Investigate why this is necessary for MSVC.
110+
// Is it a a bug in GSL? STL? MSVC?
111+
constexpr auto operator==(const pointer other) const { return _ptr + _pos == other; }
112+
constexpr auto operator!=(const pointer other) const { return !(*this == other); }
113+
#endif /* _MSC_VER */
114+
115+
constexpr auto operator==(const dyn_array_iterator& other) const
116+
{
117+
Expects(_ptr == other._ptr);
118+
Expects(_end_pos == other._end_pos);
119+
return _pos == other._pos;
120+
}
121+
122+
constexpr auto operator!=(const dyn_array_iterator& other) const
123+
{
124+
return !(*this == other);
125+
}
126+
127+
constexpr auto operator*() const -> reference
128+
{
129+
Expects(_ptr != nullptr);
130+
Expects(_pos < _end_pos);
131+
return _ptr[_pos];
132+
}
133+
134+
constexpr auto operator++() -> dyn_array_iterator&
135+
{
136+
Expects(_pos < _end_pos);
137+
_pos++;
138+
return *this;
139+
}
140+
141+
constexpr auto operator++(int)
142+
{
143+
auto rv = *this;
144+
++(*this);
145+
return rv;
146+
}
147+
148+
constexpr auto operator--() -> dyn_array_iterator&
149+
{
150+
Expects(_pos > 0);
151+
_pos--;
152+
return *this;
153+
}
154+
155+
constexpr auto operator--(int)
156+
{
157+
auto rv = *this;
158+
--(*this);
159+
return rv;
160+
}
161+
162+
constexpr auto operator+=(difference_type diff) -> dyn_array_iterator&
163+
{
164+
auto pos = gsl::narrow<difference_type>(_pos);
165+
Expects(pos + diff <= gsl::narrow<difference_type>(_end_pos));
166+
_pos = gsl::narrow<size_type>(pos + diff);
167+
return *this;
168+
}
169+
170+
constexpr auto operator-=(difference_type diff) -> dyn_array_iterator&
171+
{
172+
auto pos = gsl::narrow<difference_type>(_pos);
173+
Expects(pos >= diff);
174+
_pos = gsl::narrow<size_type>(pos - diff);
175+
return *this;
176+
}
177+
178+
constexpr auto operator+(difference_type diff) const
179+
{
180+
return dyn_array_iterator{_ptr, gsl::narrow<difference_type>(_pos) + diff, _end_pos};
181+
}
182+
183+
constexpr auto operator-(difference_type diff) const
184+
{
185+
return dyn_array_iterator{_ptr, gsl::narrow<difference_type>(_pos) - diff, _end_pos};
186+
}
187+
188+
constexpr auto operator-(const dyn_array_iterator& other) const
189+
{
190+
Expects(_ptr == other._ptr);
191+
Expects(_end_pos == other._end_pos);
192+
return gsl::narrow<difference_type>(_pos) - gsl::narrow<difference_type>(other._pos);
193+
}
194+
195+
constexpr auto operator[](size_type pos) -> reference
196+
{
197+
Expects(_pos + pos < _end_pos);
198+
return _ptr[_pos + pos];
199+
}
200+
201+
constexpr auto operator[](size_type pos) const -> const_reference
202+
{
203+
return const_cast<dyn_array_iterator&>(*this).operator[](pos);
204+
}
205+
206+
private:
207+
pointer _ptr;
208+
size_type _pos;
209+
size_type _end_pos;
210+
};
211+
} // namespace details
212+
213+
template <typename T, typename Allocator = std::allocator<T>>
214+
class dyn_array : public details::dyn_array_base<T, Allocator>
215+
{
216+
using base = details::dyn_array_base<T, Allocator>;
217+
using pointer = typename details::dyn_array_traits<T>::pointer;
218+
219+
public:
220+
using value_type = typename details::dyn_array_traits<T>::value_type;
221+
using reference = typename details::dyn_array_traits<T>::reference;
222+
using const_reference = typename details::dyn_array_traits<T>::const_reference;
223+
using iterator = details::dyn_array_iterator<T>;
224+
using const_iterator = details::dyn_array_iterator<const T>;
225+
using reverse_iterator = std::reverse_iterator<iterator>;
226+
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
227+
using difference_type = typename details::dyn_array_traits<T>::difference_type;
228+
using size_type = typename details::dyn_array_traits<T>::size_type;
229+
230+
using allocator_type = Allocator;
231+
232+
explicit constexpr dyn_array(const Allocator& alloc = {}) : base{alloc} {}
233+
234+
explicit constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})
235+
: base{count, alloc}
236+
{
237+
std::fill(begin(), end(), value);
238+
}
239+
240+
GSL_TYPE_IS_ITERATOR(InputIt)
241+
constexpr dyn_array(InputIt first, InputIt last, const Allocator& alloc = {})
242+
: base{gsl::narrow<size_type>(std::distance(first, last)), alloc}
243+
{
244+
std::copy(first, last, begin());
245+
}
246+
247+
#ifdef GSL_HAS_CONTAINER_RANGES
248+
template <typename InputRg>
249+
requires(std::ranges::input_range<InputRg>)
250+
constexpr dyn_array(std::from_range_t, InputRg&& rg, const Allocator& alloc = {})
251+
: base{gsl::narrow<size_type>(std::size(rg)), alloc}
252+
{
253+
std::ranges::copy(rg, std::ranges::begin(*this));
254+
}
255+
#endif /* GSL_HAS_RANGES */
256+
257+
constexpr explicit dyn_array(size_type count, const Allocator& alloc = {})
258+
: dyn_array{count, T{}, alloc}
259+
{}
260+
261+
constexpr dyn_array(const dyn_array& other, const Allocator& alloc = {})
262+
: dyn_array(other.begin(), other.end(), alloc)
263+
{}
264+
265+
constexpr dyn_array(std::initializer_list<T> init, const Allocator& alloc = {})
266+
: dyn_array(init.begin(), init.end(), alloc)
267+
{}
268+
269+
constexpr auto operator=(const dyn_array& other) -> dyn_array& { return dyn_array{other}; }
270+
271+
constexpr dyn_array(dyn_array&&) = delete;
272+
dyn_array& operator=(dyn_array&&) = delete;
273+
274+
constexpr auto operator==(const dyn_array& other) const
275+
{
276+
return size() == other.size() && std::equal(begin(), end(), other.begin(), other.end());
277+
}
278+
279+
constexpr auto operator!=(const dyn_array& other) const { return !(*this == other); }
280+
281+
constexpr auto size() const { return impl().count(); }
282+
283+
constexpr auto empty() const { return size() == 0; }
284+
285+
constexpr auto max_size() const { return static_cast<size_type>(-1); }
286+
287+
constexpr auto get_allocator() -> Allocator& { return *this; }
288+
289+
constexpr auto operator[](size_type pos) -> reference
290+
{
291+
Expects(pos < size());
292+
return data()[pos];
293+
}
294+
295+
constexpr auto operator[](size_type pos) const -> const_reference
296+
{
297+
return const_cast<dyn_array&>(*this)[pos];
298+
}
299+
300+
constexpr auto data() { return impl().data(); }
301+
constexpr auto data() const -> const T* { return const_cast<dyn_array&>(*this).data(); }
302+
303+
constexpr auto begin() { return iterator{data(), 0, size()}; }
304+
constexpr auto begin() const { return const_iterator{data(), 0, size()}; }
305+
306+
constexpr auto rbegin() { return reverse_iterator{end()}; }
307+
constexpr auto rbegin() const { return const_reverse_iterator{end()}; }
308+
309+
#ifdef _MSC_VER
310+
constexpr auto _Unchecked_begin() { return data(); }
311+
constexpr auto _Unchecked_begin() const -> const pointer
312+
{
313+
return const_cast<dyn_array&>(*this)._Unchecked_begin();
314+
}
315+
#endif /* _MSC_VER */
316+
317+
constexpr auto end() { return iterator{data(), size(), size()}; }
318+
constexpr auto end() const { return const_iterator{data(), size(), size()}; }
319+
320+
constexpr auto rend() { return reverse_iterator{begin()}; }
321+
constexpr auto rend() const { return const_reverse_iterator{begin()}; }
322+
323+
#ifdef _MSC_VER
324+
constexpr auto _Unchecked_end() { return data() + size(); }
325+
constexpr auto _Unchecked_end() const -> const pointer
326+
{
327+
return const_cast<dyn_array&>(*this)._Unchecked_end();
328+
}
329+
#endif /* MSC_VER */
330+
331+
private:
332+
constexpr auto impl() const { return base::impl(); }
333+
};
334+
335+
#ifdef GSL_HAS_DEDUCTION_GUIDES
336+
337+
template <class InputIt,
338+
class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
339+
dyn_array(InputIt, InputIt,
340+
Alloc = {}) -> dyn_array<typename std::iterator_traits<InputIt>::value_type, Alloc>;
341+
342+
#ifdef GSL_HAS_CONTAINER_RANGES
343+
template <std::ranges::input_range InputRg,
344+
class Alloc = std::allocator<std::ranges::range_value_t<InputRg>>>
345+
dyn_array(std::from_range_t, InputRg&&,
346+
Alloc = {}) -> dyn_array<std::ranges::range_value_t<InputRg>, Alloc>;
347+
#endif /* GSL_HAS_RANGES */
348+
349+
#endif /* GSL_HAS_DEDUCTION_GUIDES */
350+
} // namespace gsl
351+
352+
#endif /* defined(GSL_DYN_ARRAY_H) */

0 commit comments

Comments
 (0)