feat: implementation of gsl::dyn_array (#1228)

* 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

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Carson Radtkeandcopilot-swe-agent[bot] authored and GitHub committed 2026-07-13 14:53:09 -06:00
1 parent b2f6bec48e
commit a75212b9f3
11 files changed
+1364 -24

No files matched your search

+439
View File
@@ -0,0 +1,439 @@
// -*- 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>
#include <type_traits>
#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L)
#include <ranges>
#endif /* __cpp_lib_ranges >= 201911L */
namespace gsl
{
namespace details
{
template <typename T, typename Allocator = std::allocator<T>>
class dyn_array_base : public Allocator
{
using pointer = T*;
using size_type = std::size_t;
template <typename... Args>
GSL_CONSTEXPR_SINCE_CPP20 void construct(pointer ptr, Args&&... args)
{
std::allocator_traits<Allocator>::construct(static_cast<Allocator&>(*this), ptr,
std::forward<Args>(args)...);
}
GSL_CONSTEXPR_SINCE_CPP20 void destroy(pointer ptr)
{
std::allocator_traits<Allocator>::destroy(static_cast<Allocator&>(*this), ptr);
}
GSL_CONSTEXPR_SINCE_CPP20 void destroy_range(pointer first, pointer last)
{
for (; first != last; ++first) { destroy(first); }
}
GSL_CONSTEXPR_SINCE_CPP20 void rollback_construction(pointer first, pointer last)
{
destroy_range(first, last);
std::allocator_traits<Allocator>::deallocate(static_cast<Allocator&>(*this), _data,
_count);
_data = nullptr;
_count = 0;
}
protected:
constexpr auto data() const { return _data; }
constexpr auto count() const { return _count; }
GSL_CONSTEXPR_SINCE_CPP20 void resize(size_type count)
{
// This should only be called when constructing a non-forward iterator.
// It neither frees nor copies `_data`.
Expects(_data == nullptr && _count == 0);
if (count != 0)
{
_data = std::allocator_traits<Allocator>::allocate(static_cast<Allocator&>(*this),
count);
_count = count;
}
}
GSL_CONSTEXPR_SINCE_CPP20 void fill(pointer first, size_type count, const T& value)
{
pointer current = first;
try
{
for (size_type i = 0; i < count; ++i, ++current) { construct(current, value); }
} catch (...)
{
rollback_construction(first, current);
throw;
}
}
template <typename InputIt>
GSL_CONSTEXPR_SINCE_CPP20 void copy(InputIt first, InputIt last, pointer output)
{
pointer current = output;
try
{
for (; first != last; ++first, ++current) { construct(current, *first); }
} catch (...)
{
rollback_construction(output, current);
throw;
}
}
GSL_CONSTEXPR_SINCE_CPP20 void default_construct(pointer first, size_type count)
{
pointer current = first;
try
{
for (size_type i = 0; i < count; ++i, ++current) { construct(current); }
} catch (...)
{
rollback_construction(first, current);
throw;
}
}
private:
pointer _data;
size_type _count;
public:
constexpr dyn_array_base(const Allocator& alloc)
: Allocator{alloc}, _data{nullptr}, _count{0}
{
Ensures((_count == 0 && _data == nullptr) || (_count > 0 && _data != nullptr));
}
constexpr dyn_array_base(size_type count, const Allocator& alloc)
: Allocator{alloc}
, _data{count == 0 ? nullptr
: std::allocator_traits<Allocator>::allocate(
static_cast<Allocator&>(*this), count)}
, _count{count}
{
Ensures((_count == 0 && _data == nullptr) || (_count > 0 && _data != nullptr));
}
GSL_CONSTEXPR_SINCE_CPP20 ~dyn_array_base()
{
if (_data)
{
if (!std::is_trivially_destructible<T>::value)
{
destroy_range(_data, _data + _count);
}
std::allocator_traits<Allocator>::deallocate(static_cast<Allocator&>(*this), _data,
_count);
}
}
};
template <typename T>
class dyn_array_iterator
{
using size_type = std::size_t;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using const_reference = const T&;
using iterator_category = std::random_access_iterator_tag;
#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L)
constexpr dyn_array_iterator() = default;
#endif /* __cpp_lib_ranges >= 201911L */
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(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L)
constexpr operator pointer() const { return _ptr + gsl::narrow<size_type>(_pos); }
#endif /* defined(_MSC_VER) && __cpp_lib_ranges >= 201911L */
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
{
auto new_pos = gsl::narrow<difference_type>(_pos) + diff;
return dyn_array_iterator{_ptr, gsl::narrow<size_type>(new_pos), _end_pos};
}
constexpr auto operator-(difference_type diff) const { return *this + (-diff); }
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);
}
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 = T*;
public:
using value_type = T;
using reference = T&;
using const_reference = const T&;
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 = std::ptrdiff_t;
using size_type = std::size_t;
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}
{
base::fill(data(), size(), value);
}
template <typename InputIt,
std::enable_if_t<details::is_fwd_iterator<InputIt>::value, bool> = true>
constexpr dyn_array(InputIt first, InputIt last, const Allocator& alloc = {})
: base{gsl::narrow<size_type>(std::distance(first, last)), alloc}
{
base::copy(first, last, data());
}
template <typename InputIt, std::enable_if_t<!details::is_fwd_iterator<InputIt>::value &&
details::is_iterator<InputIt>::value,
bool> = true>
constexpr dyn_array(InputIt first, InputIt last, const Allocator& alloc = {}) : dyn_array{alloc}
{
std::vector<T> tmp(first, last);
base::resize(tmp.size());
base::copy(std::begin(tmp), std::end(tmp), data());
}
#if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L)
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}
{
base::copy(std::ranges::begin(rg), std::ranges::end(rg), data());
}
#endif /* __cpp_lib_containers_ranges >= 202202L */
constexpr explicit dyn_array(size_type count, const Allocator& alloc = {}) : base{count, alloc}
{
base::default_construct(data(), size());
}
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 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::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::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 T*
{
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 T*
{
return const_cast<dyn_array&>(*this)._Unchecked_end();
}
#endif /* _MSC_VER */
};
#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
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>;
#if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L)
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 /* __cpp_lib_containers_ranges >= 202202L */
#endif /* __cpp_deduction_guides >= 201703L */
} // namespace gsl
#endif /* defined(GSL_DYN_ARRAY_H) */
+3 -2
View File
@@ -1,3 +1,4 @@
// -*- C++ -*-
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2015 Microsoft Corporation. All rights reserved.
@@ -357,7 +358,7 @@ auto make_strict_not_null(T&& t) noexcept
return strict_not_null<std::remove_cv_t<std::remove_reference_t<T>>>{std::forward<T>(t)};
}
#if (defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L))
#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
// deduction guides to prevent the ctad-maybe-unsupported warning
template <class T>
@@ -365,7 +366,7 @@ not_null(T) -> not_null<T>;
template <class T>
strict_not_null(T) -> strict_not_null<T>;
#endif // ( defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L) )
#endif // defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
} // namespace gsl
+3 -2
View File
@@ -1,3 +1,4 @@
// -*- C++ -*-
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2015 Microsoft Corporation. All rights reserved.
@@ -747,7 +748,7 @@ private:
}
};
#if (defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L))
#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
// Deduction Guides
template <class Type, std::size_t Extent>
@@ -767,7 +768,7 @@ template <class Container,
class Element = std::remove_pointer_t<decltype(std::declval<const Container&>().data())>>
span(const Container&) -> span<Element>;
#endif // ( defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L) )
#endif // defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
#if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND)
#if defined(__clang__) && defined(_MSC_VER) && defined(__cplusplus) && (__cplusplus < 201703L)
+38 -1
View File
@@ -1,3 +1,4 @@
// -*- C++ -*-
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2015 Microsoft Corporation. All rights reserved.
@@ -19,9 +20,9 @@
#include "./assert" // for Expects
#include <array>
#include <cstddef> // for ptrdiff_t, size_t
#include <initializer_list> // for initializer_list
#include <iterator> // for iterator_traits
#include <limits> // for numeric_limits
#include <type_traits> // for is_signed, integral_constant
#include <utility> // for exchange, forward
@@ -86,6 +87,13 @@
#define GSL_DEPRECATED(msg)
#endif // !defined(GSL_DEPRECATED)
#if __cplusplus >= 202002L
#define GSL_CONSTEXPR_SINCE_CPP20 constexpr
#else // ^^^ since C++20 /// before C++20 vvv
#define GSL_BEFORE_CPP20
#define GSL_CONSTEXPR_SINCE_CPP20
#endif // __cplusplus >= 202002L
namespace gsl
{
//
@@ -95,6 +103,35 @@ namespace gsl
// index type for all container indexes/subscripts/sizes
using index = std::ptrdiff_t;
namespace details
{
template <typename...>
using void_t = void;
template <typename T, typename = void>
struct is_iterator : std::false_type
{
};
template <typename T>
struct is_iterator<T, void_t<typename std::iterator_traits<T>::value_type>> : std::true_type
{
};
template <typename T, typename = void>
struct is_fwd_iterator : std::false_type
{
};
template <typename T>
struct is_fwd_iterator<T, void_t<typename std::iterator_traits<T>::iterator_category>>
: std::integral_constant<
bool, std::is_base_of<std::forward_iterator_tag,
typename std::iterator_traits<T>::iterator_category>::value>
{
};
} // namespace details
// final_action allows you to ensure something gets run at the end of a scope
template <class F>
class final_action