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
354 changes: 354 additions & 0 deletions include/gsl/dyn_array
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
// -*- 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"
Comment on lines +21 to +23
Copy link
Member

Choose a reason for hiding this comment

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

Use gsl/ scoping prefix


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

#ifdef GSL_HAS_RANGES
Copy link
Member

Choose a reason for hiding this comment

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

Why the use of GSL_HAS_RANGES macro?

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

namespace gsl
{
namespace details
{
Comment on lines +33 to +36
Copy link
Member

Choose a reason for hiding this comment

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

Shorten that to namespace gsl::details ?

Copy link
Contributor

@beinhaerter beinhaerter Feb 21, 2026

Choose a reason for hiding this comment

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

From the README:

The entire implementation is provided inline in the headers under the [gsl]https://github.com/microsoft/GSL/blob/main/include/gsl) directory. The implementation generally assumes a platform that implements C++14 support.

namespace gsl::details is C++17. Unfortunately in my company we still can't use C++17 in general, so I would prefer to still support C++14 if possible.

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 = std::size_t;
};

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;

protected:
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());
}
Comment on lines +72 to +81
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

dyn_array_base allocates and deallocates raw storage via Allocator::allocate/deallocate but never constructs or destroys elements; the code then writes to that storage using std::fill/std::copy. For non–implicit-lifetime types this yields undefined behavior because the object lifetime is never started, so this class should either be constrained to implicit-lifetime/trivially constructible T or it should use std::allocator_traits<Allocator>::construct/destroy (and appropriate loops) to manage element lifetimes.

Copilot uses AI. Check for mistakes.
};

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() = default;
#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;
return *this;
}

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

constexpr auto operator--() -> dyn_array_iterator&
{
Expects(_pos > 0);
--_pos;
return *this;
}

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

constexpr auto operator+=(difference_type diff) -> dyn_array_iterator&
{
auto new_pos = gsl::narrow<difference_type>(_pos) + diff;
Expects(new_pos >= 0);
Expects(new_pos <= gsl::narrow<difference_type>(_end_pos));
_pos = gsl::narrow<size_type>(new_pos);
return *this;
}

constexpr auto operator-=(difference_type diff) -> dyn_array_iterator&
{
auto new_pos = gsl::narrow<difference_type>(_pos) - diff;
Expects(new_pos >= 0);
Expects(new_pos <= gsl::narrow<difference_type>(_end_pos));
_pos = gsl::narrow<size_type>(new_pos);
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{};
};
} // namespace details

template <typename T, typename Allocator = std::allocator<T>>
class dyn_array : private details::dyn_array_base<T, Allocator>
{
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} {}

constexpr dyn_array(size_type count, const T& value, const Allocator& alloc = {})
: 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 base::_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 base::_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 cbegin() const { return begin(); }

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

#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 cend() const { return end(); }

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

#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 */
};

#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