Files
GSL/tests/dyn_array_tests.cpp
T
2026-07-15 10:56:11 -06:00

692 lines
21 KiB
C++

#include <gtest/gtest.h>
#include "deathTestCommon.h"
#include "gsl/dyn_array"
#include <cstdlib>
#include <exception>
#include <gsl/dyn_array>
#include <gsl/util>
#include <iostream>
#include <sstream>
#include <type_traits>
// Despite using <algorithm> and <ranges> utilities in this test, they
// are not being included directly by this file as a test to ensure
// transitive inclusion via <gsl/dyn_array>.
static_assert(sizeof(gsl::dyn_array<int>) == 2 * sizeof(void*),
"gsl::dyn_array (with the default allocator) should be 16 bytes");
#if defined(__cpp_lib_concepts) && (__cpp_lib_concepts >= 202002L)
static_assert(std::input_iterator<gsl::dyn_array<int>::iterator>,
"gsl::dyn_array should expose a valid input_iterator");
#endif /* __cpp_lib_concepts >= 202002L */
#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L)
static_assert(std::ranges::input_range<gsl::dyn_array<int>>,
"gsl::dyn_array should be a valid input range");
#endif /* __cpp_lib_ranges >= 201911L */
TEST(dyn_array_tests, default_ctor)
{
gsl::dyn_array<char> diamondbacks;
EXPECT_TRUE(diamondbacks.empty());
EXPECT_EQ(diamondbacks.size(), 0);
EXPECT_EQ(diamondbacks.data(), nullptr);
}
TEST(dyn_array_tests, count_ctor)
{
gsl::dyn_array<char> athletics(10);
EXPECT_FALSE(athletics.empty());
EXPECT_EQ(athletics.size(), 10);
EXPECT_NE(athletics.data(), nullptr);
gsl::dyn_array<char> braves(0);
EXPECT_TRUE(braves.empty());
EXPECT_EQ(braves.size(), 0);
EXPECT_EQ(braves.data(), nullptr);
EXPECT_TRUE(std::all_of(braves.begin(), braves.end(), [](char c) { return c == char{}; }));
}
TEST(dyn_array_tests, count_value_ctor)
{
gsl::dyn_array<char> orioles(10, 'c');
EXPECT_FALSE(orioles.empty());
EXPECT_EQ(orioles.size(), 10);
EXPECT_NE(orioles.data(), nullptr);
EXPECT_TRUE(std::all_of(orioles.begin(), orioles.end(), [](char c) { return c == 'c'; }));
gsl::dyn_array<int> redsox(10, 42);
EXPECT_FALSE(redsox.empty());
EXPECT_EQ(redsox.size(), 10);
EXPECT_NE(redsox.data(), nullptr);
EXPECT_TRUE(std::all_of(redsox.begin(), redsox.end(), [](int i) { return i == 42; }));
}
TEST(dyn_array_tests, inputit_ctor)
{
std::vector<char> cubs(10, 'c');
gsl::dyn_array<char> whitesox(cubs.begin(), cubs.end());
EXPECT_FALSE(whitesox.empty());
EXPECT_EQ(whitesox.size(), cubs.size());
EXPECT_NE(whitesox.data(), nullptr);
EXPECT_TRUE(std::all_of(whitesox.begin(), whitesox.end(), [](char c) { return c == 'c'; }));
}
TEST(dyn_array_tests, copy_ctor)
{
gsl::dyn_array<char> reds(10, 'c');
gsl::dyn_array<char> guardians(reds);
EXPECT_FALSE(guardians.empty());
EXPECT_EQ(guardians.size(), reds.size());
EXPECT_NE(guardians.data(), nullptr);
EXPECT_TRUE(std::all_of(guardians.begin(), guardians.end(), [](char c) { return c == 'c'; }));
}
TEST(dyn_array_tests, access_operator)
{
gsl::dyn_array<char> rockies(10, 'c');
using ST = typename decltype(rockies)::size_type;
for (int i = 0; i < gsl::narrow<int>(rockies.size()); i++)
EXPECT_EQ(rockies[gsl::narrow<ST>(i)], 'c');
for (int i = 0; i < gsl::narrow<int>(rockies.size()); i++) rockies[gsl::narrow<ST>(i)] = 'r';
for (int i = 0; i < gsl::narrow<int>(rockies.size()); i++)
EXPECT_EQ(rockies[gsl::narrow<ST>(i)], 'r');
gsl::dyn_array<int> tigers(10);
for (int i = 0; i < gsl::narrow<int>(tigers.size()); i++) tigers[gsl::narrow<ST>(i)] = i;
for (int i = 0; i < gsl::narrow<int>(tigers.size()); i++)
EXPECT_EQ(tigers[gsl::narrow<ST>(i)], i);
}
TEST(dyn_array_tests, iterators)
{
gsl::dyn_array<char> astros(10, 'c');
for (auto it = astros.begin(); it != astros.end(); it++) EXPECT_EQ(*it, 'c');
for (auto it = astros.begin(); it != astros.end(); it++) *it = 'r';
for (auto it = astros.begin(); it != astros.end(); it++) EXPECT_EQ(*it, 'r');
EXPECT_TRUE(std::all_of(astros.begin(), astros.end(), [](char c) { return c == 'r'; }));
gsl::dyn_array<char> royals(10, 'c');
for (auto it = royals.begin(); it != royals.end(); ++it) EXPECT_EQ(*it, 'c');
for (auto it = royals.begin(); it != royals.end(); ++it) *it = 'r';
for (auto it = royals.begin(); it != royals.end(); ++it) EXPECT_EQ(*it, 'r');
EXPECT_TRUE(std::all_of(royals.begin(), royals.end(), [](char c) { return c == 'r'; }));
}
TEST(dyn_array_tests, range_for)
{
gsl::dyn_array<char> angels(10, 'c');
for (auto x : angels) EXPECT_EQ(x, 'c');
for (auto& x : angels) x = 'r';
for (auto x : angels) EXPECT_EQ(x, 'r');
EXPECT_TRUE(std::all_of(angels.begin(), angels.end(), [](char c) { return c == 'r'; }));
}
TEST(dyn_array_tests, use_std_algorithms)
{
gsl::dyn_array<char> dodgers(26);
std::generate(dodgers.begin(), dodgers.end(), [i = 0]() mutable { return 'a' + i++; });
char ch = 'a';
for (auto x : dodgers) EXPECT_EQ(x, ch++);
EXPECT_EQ(std::find(dodgers.begin(), dodgers.end(), 'a'), dodgers.begin());
{
auto it = std::find(dodgers.begin(), dodgers.end(), 'c');
EXPECT_EQ(std::distance(dodgers.begin(), it), 'c' - 'a');
EXPECT_EQ(std::distance(it, dodgers.begin()), 'a' - 'c');
}
{
auto it = std::lower_bound(dodgers.begin(), dodgers.end(), 'j');
EXPECT_EQ(*it, 'j');
EXPECT_EQ(std::distance(dodgers.begin(), it), 'j' - 'a');
EXPECT_EQ(std::distance(it, dodgers.begin()), 'a' - 'j');
}
EXPECT_EQ(dodgers.begin(), std::begin(dodgers));
EXPECT_EQ(dodgers.end(), std::end(dodgers));
}
#if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L)
constexpr auto default_constructed_count_dyn_array_is_constexpr()
{
gsl::dyn_array<int> values(3);
return values.size() == 3 && values[0] == 0 && values[1] == 0 && values[2] == 0;
}
TEST(dyn_array_tests, constexprness)
{
constexpr gsl::dyn_array<char> marlins;
static_assert(marlins == marlins);
static_assert(marlins.empty());
static_assert(marlins.size() == 0);
static_assert(marlins.data() == nullptr);
static_assert(marlins.begin() == marlins.end());
static_assert(std::distance(marlins.begin(), marlins.end()) == 0);
static_assert(default_constructed_count_dyn_array_is_constexpr());
}
#endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */
#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L)
TEST(dyn_array_tests, ranges)
{
gsl::dyn_array<char> brewers(26);
std::ranges::generate(brewers, [c = 'a']() mutable { return c++; });
char ch = 'a';
for (auto x : brewers) EXPECT_EQ(x, ch++);
EXPECT_EQ(std::ranges::find(brewers, 'a'), std::ranges::begin(brewers));
{
auto it = std::ranges::find(brewers, 'c');
EXPECT_EQ(std::ranges::distance(std::ranges::begin(brewers), it), 'c' - 'a');
EXPECT_EQ(std::ranges::distance(it, std::ranges::begin(brewers)), 'a' - 'c');
}
#if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L)
std::vector<char> twins(10, 'c');
gsl::dyn_array<char> mets(std::from_range, twins);
EXPECT_EQ(twins.size(), mets.size());
EXPECT_TRUE(std::ranges::all_of(mets, [](char c) { return c == 'c'; }));
#endif /* __cpp_lib_containers_ranges >= 202202L */
}
#endif /* __cpp_lib_ranges >= 201911L */
#if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L)
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif // defined(__clang__)
template <typename T, unsigned N>
struct ConstexprAllocator
{
using value_type = T;
T buf[N]{};
std::size_t sz{};
constexpr ConstexprAllocator() = default;
template <typename U>
constexpr ConstexprAllocator(const ConstexprAllocator<U, N>&) noexcept : buf{}, sz{}
{}
template <typename U>
struct rebind
{
using other = ConstexprAllocator<U, N>;
};
constexpr auto allocate(std::size_t n) -> value_type*
{
auto addr = &buf[sz];
sz += n;
return addr;
}
constexpr void deallocate(value_type*, std::size_t) noexcept {}
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif // defined(__clang__)
template <typename T1, unsigned N1, typename T2, unsigned N2>
constexpr auto operator==(const ConstexprAllocator<T1, N1>& lhs,
const ConstexprAllocator<T2, N2>& rhs) noexcept
{
return std::addressof(lhs) == std::addressof(rhs);
}
template <typename T1, unsigned N1, typename T2, unsigned N2>
constexpr auto operator!=(const ConstexprAllocator<T1, N1>& lhs,
const ConstexprAllocator<T2, N2>& rhs) noexcept
{
return !(lhs == rhs);
}
#endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */
template <typename T>
static int AllocCounter = 0;
template <typename T>
static int DeallocCounter = 0;
struct LifetimeCounter
{
static int alive_count;
int value{};
explicit LifetimeCounter(int v = 0) : value(v) { ++alive_count; }
LifetimeCounter(const LifetimeCounter& other) : value(other.value) { ++alive_count; }
~LifetimeCounter() { --alive_count; }
};
int LifetimeCounter::alive_count = 0;
struct DefaultConstructionCounter
{
static int default_constructor_count;
static int copy_constructor_count;
int value{};
DefaultConstructionCounter() { ++default_constructor_count; }
DefaultConstructionCounter(const DefaultConstructionCounter& other) : value(other.value)
{
++copy_constructor_count;
}
static void reset()
{
default_constructor_count = 0;
copy_constructor_count = 0;
}
};
int DefaultConstructionCounter::default_constructor_count = 0;
int DefaultConstructionCounter::copy_constructor_count = 0;
struct DefaultOnlyElement
{
DefaultOnlyElement() = default;
DefaultOnlyElement(const DefaultOnlyElement&) = delete;
DefaultOnlyElement& operator=(const DefaultOnlyElement&) = delete;
DefaultOnlyElement(DefaultOnlyElement&&) = delete;
DefaultOnlyElement& operator=(DefaultOnlyElement&&) = delete;
};
struct ThrowOnCopy
{
static int alive_count;
static int copy_count;
static int throw_on_copy_index;
int value{};
explicit ThrowOnCopy(int v = 0) : value(v) { ++alive_count; }
ThrowOnCopy(const ThrowOnCopy& other) : value(other.value)
{
if (copy_count == throw_on_copy_index)
{
++copy_count;
throw 42;
}
++copy_count;
++alive_count;
}
~ThrowOnCopy() { --alive_count; }
};
int ThrowOnCopy::alive_count = 0;
int ThrowOnCopy::copy_count = 0;
int ThrowOnCopy::throw_on_copy_index = -1;
template <typename T>
class Newocator
{
public:
using value_type = T;
Newocator() = default;
template <typename U>
Newocator(const Newocator<U>&) noexcept
{}
static void init()
{
AllocCounter<Newocator<T>> = 0;
DeallocCounter<Newocator<T>> = 0;
}
static void check() { EXPECT_EQ(AllocCounter<Newocator<T>>, DeallocCounter<Newocator<T>>); }
auto allocate(std::size_t n) -> value_type*
{
AllocCounter<Newocator<T>> ++;
return static_cast<value_type*>(::operator new(n * sizeof(value_type)));
}
void deallocate(value_type* p, std::size_t) noexcept
{
DeallocCounter<Newocator<T>> ++;
::operator delete(p);
}
template <typename U>
struct rebind
{
using other = Newocator<U>;
};
};
template <typename T, typename U>
constexpr auto operator==(const Newocator<T>&, const Newocator<U>&) noexcept
{
return true;
}
template <typename T, typename U>
constexpr auto operator!=(const Newocator<T>& lhs, const Newocator<U>& rhs) noexcept
{
return !(lhs == rhs);
}
template <typename T>
class OwnershipTrackingAllocator
{
public:
using value_type = T;
OwnershipTrackingAllocator() noexcept : owner_id(next_owner_id()) { ++next_owner_id(); }
explicit OwnershipTrackingAllocator(int owner) noexcept : owner_id(owner) {}
template <typename U>
OwnershipTrackingAllocator(const OwnershipTrackingAllocator<U>& other) noexcept
: owner_id(other.owner())
{}
auto allocate(std::size_t count) -> value_type*
{
static_assert(alignof(value_type) <= alignof(int),
"test allocator only supports types with int-or-smaller alignment");
auto raw =
static_cast<unsigned char*>(::operator new(sizeof(int) + count * sizeof(value_type)));
*reinterpret_cast<int*>(raw) = owner_id;
++allocation_count();
return reinterpret_cast<value_type*>(raw + sizeof(int));
}
void deallocate(value_type* pointer, std::size_t) noexcept
{
auto raw = reinterpret_cast<unsigned char*>(pointer) - sizeof(int);
if (*reinterpret_cast<int*>(raw) != owner_id) { ++mismatched_deallocation_count(); }
++deallocation_count();
::operator delete(raw);
}
auto owner() const noexcept { return owner_id; }
static void reset()
{
next_owner_id() = 1;
allocation_count() = 0;
deallocation_count() = 0;
mismatched_deallocation_count() = 0;
}
static auto mismatched_deallocations() { return mismatched_deallocation_count(); }
private:
int owner_id;
static auto next_owner_id() -> int&
{
static int value = 1;
return value;
}
static auto allocation_count() -> int&
{
static int value = 0;
return value;
}
static auto deallocation_count() -> int&
{
static int value = 0;
return value;
}
static auto mismatched_deallocation_count() -> int&
{
static int value = 0;
return value;
}
};
template <typename T, typename U>
constexpr auto operator==(const OwnershipTrackingAllocator<T>& lhs,
const OwnershipTrackingAllocator<U>& rhs) noexcept
{
return lhs.owner() == rhs.owner();
}
template <typename T, typename U>
constexpr auto operator!=(const OwnershipTrackingAllocator<T>& lhs,
const OwnershipTrackingAllocator<U>& rhs) noexcept
{
return !(lhs == rhs);
}
TEST(dyn_array_tests, custom_allocator_models_allocator)
{
using traits = std::allocator_traits<Newocator<char>>;
using ptr = traits::pointer;
static_assert(std::is_same<traits::value_type, char>::value, "allocator trait type mismatch");
static_assert(std::is_same<ptr, char*>::value, "allocator trait type mismatch");
Newocator<char> alloc;
auto p = traits::allocate(alloc, 1);
traits::deallocate(alloc, p, 1);
#if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L)
using constexpr_traits = std::allocator_traits<ConstexprAllocator<char, 10>>;
static_assert(std::is_same<constexpr_traits::value_type, char>::value,
"allocator trait type mismatch");
#endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */
}
TEST(dyn_array_tests, custom_allocator)
{
#if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L)
static constexpr gsl::dyn_array<char, ConstexprAllocator<char, 10>> mets(10, 'c');
static_assert(mets.size() == 10);
static_assert(mets[0] == 'c');
static_assert(std::all_of(std::begin(mets), std::end(mets), [](char c) { return c == 'c'; }));
#endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */
Newocator<char>::init();
{
gsl::dyn_array<char, Newocator<char>> yankees(10, 'c');
EXPECT_EQ(yankees.size(), 10);
EXPECT_TRUE(
std::all_of(std::begin(yankees), std::end(yankees), [](char c) { return c == 'c'; }));
yankees[0] = 'a';
yankees[1] = 'b';
EXPECT_EQ(yankees[0], 'a');
EXPECT_EQ(yankees[1], 'b');
EXPECT_EQ(yankees[2], 'c');
yankees.get_allocator().deallocate(yankees.get_allocator().allocate(1), 1);
}
Newocator<char>::check();
}
TEST(dyn_array_tests, non_trivial_elements_are_destroyed)
{
LifetimeCounter::alive_count = 0;
{
gsl::dyn_array<LifetimeCounter> values(5, LifetimeCounter{7});
EXPECT_EQ(values.size(), 5);
EXPECT_EQ(LifetimeCounter::alive_count, 5);
}
EXPECT_EQ(LifetimeCounter::alive_count, 0);
}
TEST(dyn_array_tests, count_constructor_default_constructs_each_element)
{
DefaultConstructionCounter::reset();
{
gsl::dyn_array<DefaultConstructionCounter> values(4);
EXPECT_EQ(values.size(), 4);
}
EXPECT_EQ(DefaultConstructionCounter::default_constructor_count, 4);
EXPECT_EQ(DefaultConstructionCounter::copy_constructor_count, 0);
}
#ifdef GSL_DYN_ARRAY_COMPILE_FAILURE_TESTS
TEST(dyn_array_compile_failure_tests, count_constructor_accepts_default_constructible_only_elements)
{
gsl::dyn_array<DefaultOnlyElement> values(4);
EXPECT_EQ(values.size(), 4);
}
#endif /* GSL_DYN_ARRAY_COMPILE_FAILURE_TESTS */
TEST(dyn_array_tests, failed_element_construction_rolls_back)
{
ThrowOnCopy::alive_count = 0;
ThrowOnCopy::copy_count = 0;
ThrowOnCopy::throw_on_copy_index = 2;
EXPECT_THROW((gsl::dyn_array<ThrowOnCopy>(5, ThrowOnCopy{1})), int);
EXPECT_EQ(ThrowOnCopy::alive_count, 0);
ThrowOnCopy::throw_on_copy_index = -1;
}
TEST(dyn_array_tests, init_list)
{
gsl::dyn_array<char> phillies = {'a', 'b', 'c'};
EXPECT_EQ(phillies.size(), 3);
EXPECT_EQ(phillies[0], 'a');
EXPECT_EQ(phillies[1], 'b');
EXPECT_EQ(phillies[2], 'c');
}
TEST(dyn_array_tests, const_operations)
{
const gsl::dyn_array<char> pirates{'a', 'b', 'c', 'd'};
EXPECT_EQ(pirates.size(), 4);
EXPECT_EQ(pirates[0], 'a');
}
TEST(dyn_array_tests, reverse_iterator)
{
const gsl::dyn_array<char> padres{'a', 'b', 'c'};
auto it = std::rbegin(padres);
EXPECT_EQ(*it++, 'c');
EXPECT_EQ(*it++, 'b');
EXPECT_EQ(*it++, 'a');
}
TEST(dyn_array_tests, random_access_iterator_arithmetic)
{
gsl::dyn_array<char> bluejays{'a', 'b', 'c', 'd'};
auto first = bluejays.begin();
auto third = first + 2;
EXPECT_EQ(*third, 'c');
EXPECT_EQ(third - first, 2);
EXPECT_EQ(*(third - 1), 'b');
EXPECT_EQ(*std::prev(third), 'b');
}
TEST(dyn_array_tests, random_access_iterator_arithmetic_accepts_negative_offsets)
{
gsl::dyn_array<char> bluejays{'a', 'b', 'c', 'd'};
auto third = bluejays.begin() + 2;
char previous{};
char next{};
EXPECT_NO_THROW(previous = *(third + -1));
EXPECT_EQ(previous, 'b');
EXPECT_NO_THROW(next = *(third - -1));
EXPECT_EQ(next, 'd');
}
TEST(dyn_array_tests, input_iterator_constructor)
{
std::istringstream stream{"n a t s"};
std::istream_iterator<char> first{stream};
const std::istream_iterator<char> last{};
gsl::dyn_array<char> nationals(first, last);
ASSERT_EQ(nationals.size(), 4);
EXPECT_EQ(nationals[0], 'n');
EXPECT_EQ(nationals[1], 'a');
EXPECT_EQ(nationals[2], 't');
EXPECT_EQ(nationals[3], 's');
}
TEST(dyn_array_tests, contract_violations)
{
const auto terminateHandler = std::set_terminate([] {
std::cerr << "Expected Death. dyn_array_contract_violations";
std::abort();
});
const auto expected = GetExpectedDeathString(terminateHandler);
gsl::dyn_array<char> values(3, 'v');
gsl::dyn_array<char> other(3, 'o');
EXPECT_DEATH(values[values.size()], expected);
EXPECT_DEATH((void) *values.end(), expected);
EXPECT_DEATH(++values.end(), expected);
EXPECT_DEATH(--values.begin(), expected);
EXPECT_DEATH((void) (values.begin() == other.begin()), expected);
}
#ifdef _MSC_VER
TEST(dyn_array_tests, unchecked_iterators)
{
gsl::dyn_array<char> values;
const gsl::dyn_array<char> const_values(3, 'v');
EXPECT_TRUE((std::is_same<decltype(const_values._Unchecked_begin()), const char*>::value));
EXPECT_TRUE((std::is_same<decltype(const_values._Unchecked_end()), const char*>::value));
std::size_t count = 0;
for (const auto value : const_values)
{
EXPECT_EQ(value, 'v');
++count;
}
EXPECT_EQ(count, const_values.size());
EXPECT_EQ(values._Unchecked_begin(), nullptr);
EXPECT_EQ(values._Unchecked_end(), nullptr);
}
#endif /* _MSC_VER */
TEST(DynArrayTests, TypeConsistency)
{
static_assert(std::is_same<gsl::dyn_array<int>::value_type, int>::value, "Value type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::reference, int&>::value,
"Reference type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::const_reference, const int&>::value,
"Const reference type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::iterator::value_type, int>::value,
"Iterator value type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::iterator::reference, int&>::value,
"Iterator reference type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::iterator::const_reference, const int&>::value,
"Iterator const reference type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::size_type, std::size_t>::value,
"Size type mismatch");
static_assert(std::is_same<gsl::dyn_array<int>::difference_type, std::ptrdiff_t>::value,
"Difference type mismatch");
}
#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L)
TEST(dyn_array_tests, deduction_guides)
{
std::vector<char> giants{10};
#if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L)
gsl::dyn_array mariners(std::from_range, giants);
#endif /* __cpp_lib_containers_ranges >= 202202L */
gsl::dyn_array cardinals(std::begin(giants), std::end(giants));
}
#endif /* __cpp_deduction_guides >= 201703L */