Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ span_p | &#x26
[cu32zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `const char32_t`
[**2. Owners**][cg-owners] | |
stack_array | ☐ | A stack-allocated array
dyn_array | ☐ | A heap-allocated array
dyn_array | ☑ | A heap-allocated array
[**3. Assertions**][cg-assertions] | |
[Expects](docs/headers.md#user-content-H-assert-expects) | ☑ | A precondition assertion; on failure it terminates
[Ensures](docs/headers.md#user-content-H-assert-ensures) | ☑ | A postcondition assertion; on failure it terminates
Expand Down
5 changes: 5 additions & 0 deletions docs/headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ See [GSL: Guidelines support library](https://isocpp.github.io/CppCoreGuidelines
- [`<algorithms>`](#user-content-H-algorithms)
- [`<assert>`](#user-content-H-assert)
- [`<byte>`](#user-content-H-byte)
- [`<dyn_array>`](#user-content-H-dyn_array)
- [`<gsl>`](#user-content-H-gsl)
- [`<narrow>`](#user-content-H-narrow)
- [`<pointers>`](#user-content-H-pointers)
Expand Down Expand Up @@ -155,6 +156,10 @@ constexpr byte to_byte() noexcept;

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

## <a name="H-dyn_array" />`<dyn_array>`

# TODO (@carsonradtke)

Comment on lines +159 to +162
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The <dyn_array> section in headers.md is currently just a TODO placeholder, while other headers in this document provide concrete API descriptions. Given that README.md now advertises dyn_array as implemented, this section should be updated to document the main public types and functions before merging.

Copilot uses AI. Check for mistakes.
## <a name="H-gsl" />`<gsl>`

This header is a convenience header that includes all other [GSL headers](#user-content-H).
Expand Down
352 changes: 352 additions & 0 deletions include/gsl/dyn_array
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
// -*- C++ -*-
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2026 Microsoft Corporation. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////

#ifndef GSL_DYN_ARRAY_H
#define GSL_DYN_ARRAY_H

#include "./assert"
#include "./narrow"
#include "./util"

#include <algorithm>
#include <iterator>
#include <memory>

#ifdef GSL_HAS_RANGES
#include <ranges>
#endif /* GSL_HAS_RANGES */

namespace gsl
{
namespace details
{
template <typename T>
struct dyn_array_traits
{
using value_type = T;
using pointer = T*;
using reference = T&;
using const_reference = const T&;
using difference_type = std::ptrdiff_t;
using size_type = decltype(sizeof(0));
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this better than the shorter std::size_t?

https://en.cppreference.com/w/cpp/language/sizeof.html

Copy link
Member Author

Choose a reason for hiding this comment

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

Didn't need a #include. But now we already have it for ptrdiff_t, so I'll update it.

};

template <typename T, typename Allocator = std::allocator<T>>
class dyn_array_base : public Allocator
{
using pointer = typename dyn_array_traits<T>::pointer;
using size_type = typename dyn_array_traits<T>::size_type;

const class dyn_array_impl
{
public:
constexpr dyn_array_impl(pointer data, size_type count) : _data{data}, _count{count}
{
Ensures((_count == 0 && _data == nullptr) || (_count > 0 && _data != nullptr));
}

constexpr auto data() const { return _data; }

constexpr auto count() const { return _count; }

private:
pointer _data;
size_type _count;
} _impl;

public:
constexpr dyn_array_base(const Allocator& alloc) : Allocator{alloc}, _impl{nullptr, 0} {}
constexpr dyn_array_base(size_type count, const Allocator& alloc)
: Allocator{alloc}, _impl{count == 0 ? nullptr : Allocator::allocate(count), count}
{}

GSL_CONSTEXPR_SINCE_CPP20 ~dyn_array_base()
{
if (impl().data()) Allocator::deallocate(impl().data(), impl().count());
}

constexpr auto impl() const -> const dyn_array_impl& { return _impl; }
};

template <typename T>
class dyn_array_iterator
{
using size_type = typename dyn_array_traits<T>::size_type;

public:
using difference_type = typename dyn_array_traits<T>::difference_type;
using value_type = typename dyn_array_traits<T>::value_type;
using pointer = typename dyn_array_traits<T>::pointer;
using reference = typename dyn_array_traits<T>::reference;
using const_reference = typename dyn_array_traits<T>::const_reference;
using iterator_category = std::random_access_iterator_tag;

#ifdef GSL_HAS_RANGES
constexpr dyn_array_iterator() : dyn_array_iterator(nullptr, 0, 0) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
constexpr dyn_array_iterator() : dyn_array_iterator(nullptr, 0, 0) {}
constexpr dyn_array_iterator() : dyn_array_iterator{ nullptr, 0, 0 } {}

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es23-prefer-the--initializer-syntax

or even better

        constexpr dyn_array_iterator() = default;

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c48-prefer-default-member-initializers-to-member-initializers-in-constructors-for-constant-initializers

#endif /* GSL_HAS_RANGES */

constexpr dyn_array_iterator(pointer ptr, size_type pos, size_type end_pos)
: _ptr{ptr}, _pos{pos}, _end_pos{end_pos}
{
Ensures((_ptr != nullptr && _end_pos > 0) || (_ptr == nullptr && _end_pos == 0));
Ensures(_pos <= _end_pos);
}

#if defined(_MSC_VER) && defined(GSL_HAS_RANGES)
// TODO (@carsonradtke): Investigate why this is necessary for MSVC.
// Is it a a bug in GSL? STL? MSVC?
constexpr operator pointer() const { return _ptr + gsl::narrow<size_type>(_pos); }
#endif /* defined(_MSC_VER) && defined(GSL_HAS_RANGES) */

constexpr auto operator==(const dyn_array_iterator& other) const
{
Expects(_ptr == other._ptr);
Expects(_end_pos == other._end_pos);
return _pos == other._pos;
}

constexpr auto operator!=(const dyn_array_iterator& other) const
{
return !(*this == other);
}

constexpr auto operator*() const -> reference
{
Expects(_ptr != nullptr);
Expects(_pos < _end_pos);
return _ptr[_pos];
}

constexpr auto operator++() -> dyn_array_iterator&
{
Expects(_pos < _end_pos);
_pos++;
Copy link
Contributor

Choose a reason for hiding this comment

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

return *this;
}

constexpr auto operator++(int)
{
auto rv = *this;
++(*this);
return rv;
}

constexpr auto operator--() -> dyn_array_iterator&
{
Expects(_pos > 0);
_pos--;
Copy link
Contributor

Choose a reason for hiding this comment

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

return *this;
}

constexpr auto operator--(int)
{
auto rv = *this;
--(*this);
return rv;
}

constexpr auto operator+=(difference_type diff) -> dyn_array_iterator&
{
auto pos = gsl::narrow<difference_type>(_pos);
Expects(pos + diff <= gsl::narrow<difference_type>(_end_pos));
Copy link
Contributor

Choose a reason for hiding this comment

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

diff is signed, but here is no check for underflow as in int a=3; int b=-4; my_dyn_array.begin() + a + b (-> test coverage?).

_pos = gsl::narrow<size_type>(pos + diff);
return *this;
}

constexpr auto operator-=(difference_type diff) -> dyn_array_iterator&
{
auto pos = gsl::narrow<difference_type>(_pos);
Expects(pos >= diff);
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to above.

diff is signed, but here is no check for overflow as in int a=3; int b=-4; my_dyn_array.end() - a - b (-> test coverage?).

_pos = gsl::narrow<size_type>(pos - diff);
return *this;
}

constexpr auto operator+(difference_type diff) const
{
return dyn_array_iterator{_ptr, _pos + gsl::narrow<size_type>(diff), _end_pos};
}

constexpr auto operator-(difference_type diff) const
{
return dyn_array_iterator{_ptr, _pos + gsl::narrow<size_type>(diff), _end_pos};
}

constexpr auto operator-(const dyn_array_iterator& other) const
{
Expects(_ptr == other._ptr);
Expects(_end_pos == other._end_pos);
return gsl::narrow<difference_type>(_pos) - gsl::narrow<difference_type>(other._pos);
}

constexpr auto operator[](size_type pos) -> reference
{
Expects(_pos + pos < _end_pos);
return _ptr[_pos + pos];
}

constexpr auto operator[](size_type pos) const -> const_reference
{
return const_cast<dyn_array_iterator&>(*this).operator[](pos);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this triggers C26492 and should be GSL_SUPPRESSed.

}

private:
pointer _ptr;
size_type _pos;
size_type _end_pos;
Comment on lines +207 to +209
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pointer _ptr;
size_type _pos;
size_type _end_pos;
pointer _ptr{};
size_type _pos{};
size_type _end_pos{};

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c48-prefer-default-member-initializers-to-member-initializers-in-constructors-for-constant-initializers

};
} // namespace details

template <typename T, typename Allocator = std::allocator<T>>
class dyn_array : public details::dyn_array_base<T, Allocator>
Copy link
Contributor

Choose a reason for hiding this comment

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

This exposes the base stuff to the clients and the base class publicly inherits from Allocator. Is it really intended that a dyn_array exposes the Allocator methods? MSVC's std::vector does not do so.

{
using base = details::dyn_array_base<T, Allocator>;
using pointer = typename details::dyn_array_traits<T>::pointer;

public:
using value_type = typename details::dyn_array_traits<T>::value_type;
using reference = typename details::dyn_array_traits<T>::reference;
using const_reference = typename details::dyn_array_traits<T>::const_reference;
using iterator = details::dyn_array_iterator<T>;
using const_iterator = details::dyn_array_iterator<const T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using difference_type = typename details::dyn_array_traits<T>::difference_type;
using size_type = typename details::dyn_array_traits<T>::size_type;

using allocator_type = Allocator;

explicit constexpr dyn_array(const Allocator& alloc = {}) : base{alloc} {}

explicit constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
explicit constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})
constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})

This ctor takes at least two params, so explicit is not needed.

: base{count, alloc}
{
std::fill(begin(), end(), value);
Copy link
Contributor

Choose a reason for hiding this comment

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

UB. The base class called the allocator but did not construct any object. So you cannot call fill or copy (below).

gemini says someone should call std::allocator_traits::construct() after allocate and std::allocator_traits::destroy before deallocate.

Maybe a test with a class can be added for test coverage?

}

GSL_TYPE_IS_ITERATOR(InputIt)
constexpr dyn_array(InputIt first, InputIt last, const Allocator& alloc = {})
: base{gsl::narrow<size_type>(std::distance(first, last)), alloc}
{
std::copy(first, last, begin());
}

#ifdef GSL_HAS_CONTAINER_RANGES
template <typename InputRg>
requires(std::ranges::input_range<InputRg>)
constexpr dyn_array(std::from_range_t, InputRg&& rg, const Allocator& alloc = {})
: base{gsl::narrow<size_type>(std::size(rg)), alloc}
{
std::ranges::copy(rg, std::ranges::begin(*this));
}
#endif /* GSL_HAS_RANGES */

constexpr explicit dyn_array(size_type count, const Allocator& alloc = {})
: dyn_array{count, T{}, alloc}
{}

constexpr dyn_array(const dyn_array& other, const Allocator& alloc = {})
: dyn_array(other.begin(), other.end(), alloc)
{}

constexpr dyn_array(std::initializer_list<T> init, const Allocator& alloc = {})
: dyn_array(init.begin(), init.end(), alloc)
{}

constexpr auto operator=(const dyn_array& other) -> dyn_array& { return dyn_array{other}; }

constexpr dyn_array(dyn_array&&) = delete;
dyn_array& operator=(dyn_array&&) = delete;

constexpr auto operator==(const dyn_array& other) const
{
return size() == other.size() && std::equal(begin(), end(), other.begin(), other.end());
}

constexpr auto operator!=(const dyn_array& other) const { return !(*this == other); }

constexpr auto size() const { return impl().count(); }

constexpr auto empty() const { return size() == 0; }

constexpr auto max_size() const { return static_cast<size_type>(-1); }

constexpr auto get_allocator() -> Allocator& { return *this; }

constexpr auto operator[](size_type pos) -> reference
{
Expects(pos < size());
return data()[pos];
}

constexpr auto operator[](size_type pos) const -> const_reference
{
return const_cast<dyn_array&>(*this)[pos];
}

constexpr auto data() { return impl().data(); }
constexpr auto data() const -> const T* { return const_cast<dyn_array&>(*this).data(); }

constexpr auto begin() { return iterator{data(), 0, size()}; }
constexpr auto begin() const { return const_iterator{data(), 0, size()}; }

constexpr auto rbegin() { return reverse_iterator{end()}; }
constexpr auto rbegin() const { return const_reverse_iterator{end()}; }

#ifdef _MSC_VER
constexpr auto _Unchecked_begin() { return data(); }
constexpr auto _Unchecked_begin() const -> const pointer
{
return const_cast<dyn_array&>(*this)._Unchecked_begin();
}
#endif /* _MSC_VER */

constexpr auto end() { return iterator{data(), size(), size()}; }
constexpr auto end() const { return const_iterator{data(), size(), size()}; }

constexpr auto rend() { return reverse_iterator{begin()}; }
constexpr auto rend() const { return const_reverse_iterator{begin()}; }

#ifdef _MSC_VER
constexpr auto _Unchecked_end() { return data() + size(); }
constexpr auto _Unchecked_end() const -> const pointer
{
return const_cast<dyn_array&>(*this)._Unchecked_end();
}
#endif /* MSC_VER */
Copy link
Contributor

Choose a reason for hiding this comment

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

cbegin, cend, crbegin and crend are missing.


private:
constexpr auto impl() const { return base::impl(); }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? What is the benefit of adding this function? Isn't it sufficient that the base class exposes the function? If the intention is to make base::impl private, a using base::impl; would be sufficient.

};

#ifdef GSL_HAS_DEDUCTION_GUIDES

template <class InputIt,
class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
dyn_array(InputIt, InputIt,
Alloc = {}) -> dyn_array<typename std::iterator_traits<InputIt>::value_type, Alloc>;

#ifdef GSL_HAS_CONTAINER_RANGES
template <std::ranges::input_range InputRg,
class Alloc = std::allocator<std::ranges::range_value_t<InputRg>>>
dyn_array(std::from_range_t, InputRg&&,
Alloc = {}) -> dyn_array<std::ranges::range_value_t<InputRg>, Alloc>;
#endif /* GSL_HAS_RANGES */

#endif /* GSL_HAS_DEDUCTION_GUIDES */
} // namespace gsl

#endif /* defined(GSL_DYN_ARRAY_H) */
Loading