Skip to content

chore: Add VC6-compatible std::array - #3310

Merged
xezon merged 3 commits into
TheSuperHackers:mainfrom
stephanmeesters:chore/vc6-stdarray
Sep 18, 2026
Merged

xezon merged 3 commits into
TheSuperHackers:mainfrom
stephanmeesters:chore/vc6-stdarray

Conversation

@stephanmeesters

@stephanmeesters stephanmeesters commented Sep 17, 2026

Copy link
Copy Markdown

See conversation #3245 (comment)

This adds a vibe-coded implementation of std::array that will work with the vc6 compiler.

Example usage:

  #include <Utility/array_adapter.h>
  #include <algorithm>

  void example()
  {
      std::array<Int, 3> values = {{30, 10, 20}};

      values[0] = 15;
      std::sort(values.begin(), values.end()); // 10, 15, 20

      int first = values.front(); // 10
      int last  = values.back();  // 20

      values.fill(0); // All three elements become zero
  }
Vibe-coded test suite
#include "Utility/array_adapter.h"

#include <algorithm>
#include <cassert>
#include <deque>
#include <stdexcept>

struct Tracked
{
	static int alive;
	Tracked() { ++alive; }
	Tracked(const Tracked&) { ++alive; }
	~Tracked() { --alive; }
};
int Tracked::alive = 0;

class NoDefaultConstructor
{
private:
	NoDefaultConstructor();
};

int main()
{
	std::array<int, 4> values = {{4, 1, 3, 2}};
	assert(values.size() == 4 && values.max_size() == 4 && !values.empty());
	assert(sizeof(values) == 4 * sizeof(int));
	assert(values.data() == &values[0]);
	assert(values.end() - values.begin() == 4);
	assert(values.front() == 4 && values.back() == 2);
	assert(values.rbegin()[1] == 3 && values.rend() - values.rbegin() == 4);
	std::sort(values.begin(), values.end());
	assert(values.front() == 1 && values.back() == 4);
	values[1] = 8;
	assert(values.at(1) == 8);

	const std::array<int, 4>& view = values;
	assert(view.begin() == view.cbegin() && view.end() == view.cend());
	assert(view.data() == &view[0] && view.at(1) == 8);
	assert(view.front() == 1 && view.back() == 4);
	assert(*view.rbegin() == 4 && view.rbegin() == view.crbegin());
	assert(view.rend() == view.crend());

	bool threw = false;
	try { values.at(values.size()) = 0; }
	catch (const std::out_of_range&) { threw = true; }
	assert(threw);
	threw = false;
	try { (void)view.at(static_cast<std::array<int, 4>::size_type>(-1)); }
	catch (const std::out_of_range&) { threw = true; }
	assert(threw);

	std::array<int, 4> copy = values;
	assert(copy == values && !(copy != values));
	copy.fill(9);
	assert(copy.front() == 9 && copy.back() == 9);
	assert(values < copy && copy > values && values <= copy && copy >= values);
	assert(values <= values && values >= values);
	int* originalStorage = values.data();
	values.swap(copy);
	assert(values.data() == originalStorage && values.front() == 9 && copy.front() == 1);
	std::swap(values, copy);
	assert(values.front() == 1 && copy.front() == 9);
	copy = values;
	copy[0] = 7;
	assert(values.front() == 1);
	std::deque<int> sequence;
	for (std::array<int, 4>::const_iterator it = view.begin(); it != view.end(); ++it)
	{
		sequence.push_back(*it);
	}
	assert(sequence.size() == values.size() && sequence[1] == 8);

	std::array<int, 3> zeroed = {{0}};
	assert(zeroed[0] == 0 && zeroed[1] == 0 && zeroed[2] == 0);
	std::array<int, 1> single = {{5}};
	assert(&single.front() == &single.back() && *single.rbegin() == 5);

	std::array<int, 0> empty;
	std::array<int, 0> anotherEmpty;
	assert(empty.empty() && empty.size() == 0 && empty.max_size() == 0);
	assert(empty.begin() == empty.end() && empty.rbegin() == empty.rend());
	empty.fill(1);
	empty.swap(anotherEmpty);
	std::swap(empty, anotherEmpty);
	assert(empty == anotherEmpty && !(empty < anotherEmpty));
	threw = false;
	try { (void)empty.at(0); }
	catch (const std::out_of_range&) { threw = true; }
	assert(threw);

	{
		std::array<Tracked, 3> tracked;
		assert(Tracked::alive == 3);
		std::array<Tracked, 0> noElements;
		assert(noElements.empty());
		const int aliveBeforeCopy = Tracked::alive;
		std::array<Tracked, 3> trackedCopy = tracked;
		assert(Tracked::alive == aliveBeforeCopy + 3);
	}
	assert(Tracked::alive == 0);
	std::array<NoDefaultConstructor, 0> noConstruction;
	assert(noConstruction.empty());
	return 0;
}
Extended coverage test
#include <Utility/array_adapter.h>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <stdexcept>
#include <string>

static unsigned checks = 0;

static void check(bool condition, const char* expression, int line)
{
	++checks;
	if (!condition)
	{
		fprintf(stderr, "line %d: %s\n", line, expression);
		exit(1);
	}
}

// Unlike assert(), these checks also run when NDEBUG is defined.
#define CHECK(expression) check(!!(expression), #expression, __LINE__)

template <class T, size_t N>
static void comparisons(const std::array<T, N>& a, const std::array<T, N>& b, int order)
{
	CHECK((a == b) == (order == 0));
	CHECK((a != b) == (order != 0));
	CHECK((a < b) == (order < 0));
	CHECK((a > b) == (order > 0));
	CHECK((a <= b) == (order <= 0));
	CHECK((a >= b) == (order >= 0));
}

template <class T>
static void nonempty()
{
	typedef std::array<T, 3> Array;
	Array a;
	a[0] = T(3); a[1] = T(1); a[2] = T(2);
	const Array& c = a;
	typename Array::value_type value = T(3);
	typename Array::size_type size = a.size();
	typename Array::difference_type distance = a.end() - a.begin();
	typename Array::reference reference = a[0];
	typename Array::const_reference const_reference = c[0];
	typename Array::pointer pointer = a.data();
	typename Array::const_pointer const_pointer = c.data();
	CHECK(size == 3 && distance == 3 && a.max_size() == 3 && !a.empty());
	CHECK(reference == value && const_reference == value);
	CHECK(pointer == &a[0] && const_pointer == &c[0]);
	CHECK(&*a.begin() == a.data() && &*(a.end() - 1) == a.data() + 2);
	CHECK(&*c.begin() == c.data() && &*(c.end() - 1) == c.data() + 2);
	CHECK(a.cbegin() == c.begin() && a.cend() == c.end());
	CHECK(c.cbegin() == c.begin() && c.cend() == c.end());
	CHECK(a.front() == T(3) && c.front() == T(3));
	CHECK(a.back() == T(2) && c.back() == T(2));
	a.front() = T(4);
	a.back() = T(5);
	CHECK(a[0] == T(4) && a[2] == T(5));
	a.at(1) = T(6);
	CHECK(c.at(0) == T(4) && c.at(1) == T(6) && c.at(2) == T(5));
	CHECK(a.at(0) == T(4) && a.at(2) == T(5));

	typename Array::iterator it = a.begin();
	typename Array::const_iterator ci = c.begin();
	for (size_t i = 0; i < a.size(); ++i, ++it, ++ci)
	{
		CHECK(*it == a[i] && *ci == c[i]);
	}
	CHECK(it == a.end() && ci == c.end());
	typename Array::reverse_iterator ri = a.rbegin();
	typename Array::const_reverse_iterator cri = c.rbegin();
	for (size_t j = a.size(); j > 0; --j, ++ri, ++cri)
	{
		CHECK(*ri == a[j - 1] && *cri == c[j - 1]);
	}
	CHECK(ri == a.rend() && cri == c.rend());
	CHECK(a.crbegin() == c.rbegin() && a.crend() == c.rend());
	CHECK(c.crbegin() == c.rbegin() && c.crend() == c.rend());
	*a.rbegin() = T(7);
	CHECK(c.back() == T(7));

	Array copy = a;
	comparisons(a, copy, 0);
	copy.fill(T(8));
	CHECK(copy[0] == T(8) && copy[1] == T(8) && copy[2] == T(8));
	a.swap(copy);
	CHECK(a[0] == T(8) && a[1] == T(8) && a[2] == T(8));
	CHECK(copy[0] == T(4) && copy[1] == T(6) && copy[2] == T(7));
	std::swap(a, copy);
	CHECK(a[0] == T(4) && a[1] == T(6) && a[2] == T(7));
	CHECK(copy[0] == T(8) && copy[1] == T(8) && copy[2] == T(8));
	a.swap(a);
	CHECK(a[0] == T(4) && a[1] == T(6) && a[2] == T(7));
	copy = a;
	comparisons(a, copy, 0);

	// Exercise both out-of-range overloads at the boundary and at size_t(-1).
	const size_t invalid[] = {3, static_cast<size_t>(-1)};
	for (size_t k = 0; k < 2; ++k)
	{
		bool caught = false;
		try { a.at(invalid[k]); }
		catch (const std::out_of_range&) { caught = true; }
		CHECK(caught);
		caught = false;
		try { c.at(invalid[k]); }
		catch (const std::out_of_range&) { caught = true; }
		CHECK(caught);
	}

	// Compare every pair of three-element arrays whose elements are 0, 1, or 2.
	// Base-three numeric order provides an independent ordering oracle.
	for (int left = 0; left < 27; ++left)
	{
		Array lhs;
		lhs[0] = T(left / 9); lhs[1] = T((left / 3) % 3); lhs[2] = T(left % 3);
		for (int right = 0; right < 27; ++right)
		{
			Array rhs;
			rhs[0] = T(right / 9); rhs[1] = T((right / 3) % 3); rhs[2] = T(right % 3);
			comparisons(lhs, rhs, left < right ? -1 : (left > right ? 1 : 0));
		}
	}
}

template <class T>
static void empty()
{
	std::array<T, 0> a, b;
	const std::array<T, 0>& c = a;
	CHECK(a.size() == 0 && a.max_size() == 0 && a.empty());
	CHECK(a.begin() == a.end() && c.begin() == c.end());
	CHECK(a.cbegin() == a.cend() && c.cbegin() == c.cend());
	CHECK(a.data() == c.data());
	CHECK(a.rbegin() == a.rend() && c.rbegin() == c.rend());
	CHECK(a.crbegin() == a.crend() && c.crbegin() == c.crend());
	a.fill(T(1));
	a.swap(b);
	std::swap(a, b);
	b = a;
	comparisons(a, b, 0);
	bool caught = false;
	try { a.at(0); }
	catch (const std::out_of_range&) { caught = true; }
	CHECK(caught);
	caught = false;
	try { c.at(0); }
	catch (const std::out_of_range&) { caught = true; }
	CHECK(caught);
	// front(), back(), and operator[] have no valid index for an empty array.
}

struct Value
{
	Value() : value(0) {}
	explicit Value(int n) : value(n) {}
	Value(const Value& other) : value(other.value) {}
	Value& operator=(const Value& other) { value = other.value; return *this; }
	bool operator==(const Value& other) const { return value == other.value; }
	bool operator<(const Value& other) const { return value < other.value; }
	int value;
};

class NoDefault
{
private:
	NoDefault();
};

static void special_cases()
{
	std::array<int, 1> one = {{42}};
	CHECK(one.size() == 1 && one.front() == one.back() && one.at(0) == 42);
	std::array<int, 3> partial = {{1}};
	CHECK(partial[0] == 1 && partial[1] == 0 && partial[2] == 0);
	std::array<int, 3> zeros = {{0}};
	CHECK(zeros[0] == 0 && zeros[1] == 0 && zeros[2] == 0);
	std::array<NoDefault, 0> no_default;
	CHECK(no_default.empty());
	// VC6 rejects aggregate initialization of const elements; zero-sized
	// const-element storage still needs to compile and support read-only access.
	std::array<const int, 0> const_elements;
	CHECK(const_elements.empty() && const_elements.begin() == const_elements.end());
	std::array<std::string, 2> words;
	words[0] = "second";
	words[1] = "first";
	std::sort(words.begin(), words.end());
	CHECK(words.front() == "first" && words.back() == "second");
	words.fill("filled");
	CHECK(words[0] == "filled" && words[1] == "filled");
	// Original example.
	std::array<int, 3> values = {{30, 10, 20}};
	values[0] = 15;
	std::sort(values.begin(), values.end());
	CHECK(values.front() == 10 && values[1] == 15 && values.back() == 20);
	values.fill(0);
	CHECK(values[0] == 0 && values[1] == 0 && values[2] == 0);
}

int array_adapter_header_test();

int main()
{
	CHECK(array_adapter_header_test() == 0);
	nonempty<int>();
	nonempty<unsigned char>();
	nonempty<Value>();
	empty<int>();
	empty<unsigned char>();
	empty<Value>();
	special_cases();
#ifdef _MSC_VER
	printf("_MSC_VER=%d _MSC_FULL_VER=%ld ", _MSC_VER, static_cast<long>(_MSC_FULL_VER));
#endif
#ifdef __clang__
	printf("Clang=%s ", __clang_version__);
#elif defined(__GNUC__)
	printf("GCC=%s ", __VERSION__);
#endif
#ifdef USING_STLPORT
	printf("STLport ");
#endif
	printf("%u checks passed\n", checks);
	return 0;
}

@coderabbitai

This comment was marked as spam.

@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge because the latest condition rewrite preserves the intended VC6-versus-modern compiler selection and no actionable issue remains.

Summary

This PR adds a VC6-compatible std::array adapter and registers its header with the Utility source set.

  • Modern and non-MSVC compilers continue to use the standard <array> implementation.
  • MSVC versions older than 1300 receive a C++98-compatible container implementation.
  • The change since the previous review only rewrites the compiler-selection condition without changing its behavior.

Reviews (3) · Last reviewed commit: "Fix guard"

coderabbitai[bot]

This comment was marked as spam.

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks promising.

Comment thread Dependencies/Utility/Utility/array_adapter.h Outdated
Comment thread Dependencies/Utility/Utility/array_adapter.h Outdated
Comment thread Dependencies/Utility/Utility/array_adapter.h
@xezon xezon added the Platform Work towards platform support, such as Linux, MacOS label Sep 17, 2026
Comment thread Dependencies/Utility/Utility/array_adapter.h Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Remove the template disambiguator in the VC6 branch. · array_adapter.h:89

Dependencies/Utility/Utility/array_adapter.h:89
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the template disambiguator in the VC6 branch. When _MSC_VER < 1300, MSVC 6 does not support the A::template member<U> form. This declaration can therefore block adapter compilation. Use storage<N>::for_type<T> at line 89.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: a09fa090-6609-4319-b9a1-0fa95ed316b5

📥 Commits

Reviewing files that changed from the base of the PR and between 188ddd1 and 31f7331.

📒 Files selected for processing (1)
  • Dependencies/Utility/Utility/array_adapter.h

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@coderabbitai
coderabbitai Bot requested a review from xezon September 17, 2026 16:02
@Caball009

Caball009 commented Sep 17, 2026

Copy link
Copy Markdown

Can the test code (in the first post) be updated so that it's not missing any context?

@stephanmeesters

Copy link
Copy Markdown
Author

Can the test code (in the first post) be updated so that it's not missing any context?

Done

@Caball009 Caball009 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks alright.

@stephanmeesters stephanmeesters changed the title chore: Add vc6-compatible std::array chore: Add VC6-compatible std::array Sep 17, 2026
@xezon
xezon merged commit 7a695b5 into TheSuperHackers:main Sep 18, 2026
24 checks passed
@stephanmeesters
stephanmeesters deleted the chore/vc6-stdarray branch September 18, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Platform Work towards platform support, such as Linux, MacOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants