Skip to content

Commit 53786a9

Browse files
committed
Improved Iterable to support mapped types and containers not providing subscript operators
1 parent 8b10d74 commit 53786a9

3 files changed

Lines changed: 387 additions & 11 deletions

File tree

lib/public/StormByte/iterable.hxx

Lines changed: 165 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <StormByte/exception.hxx>
4+
#include <StormByte/type_traits.hxx>
45

56
#include <iterator>
67
#include <utility>
@@ -307,18 +308,18 @@ namespace StormByte {
307308
*/
308309
iterator begin() noexcept { return iterator(m_data.begin()); }
309310

310-
/**
311-
* @brief Gets end iterator
312-
* @return Iterator to the end of the container
313-
*/
314-
iterator end() noexcept { return iterator(m_data.end()); }
315-
316311
/**
317312
* @brief Gets const begin iterator
318313
* @return Const iterator to the beginning of the container
319314
*/
320315
const_iterator begin() const noexcept { return const_iterator(m_data.begin()); }
321316

317+
/**
318+
* @brief Gets end iterator
319+
* @return Iterator to the end of the container
320+
*/
321+
iterator end() noexcept { return iterator(m_data.end()); }
322+
322323
/**
323324
* @brief Gets const end iterator
324325
* @return Const iterator to the end of the container
@@ -390,33 +391,186 @@ namespace StormByte {
390391
* @param i Index of the element to access
391392
* @return Reference to the element at index i
392393
*/
393-
reference operator[](size_type i) {
394+
reference operator[](size_type i)
395+
requires Type::HasSubscript<Container, size_type> {
394396
if (i >= m_data.size())
395397
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
396398
return m_data[i];
397399
}
398400

401+
reference operator[](size_type i)
402+
requires (!Type::HasSubscript<Container, size_type>) {
403+
if (i >= m_data.size())
404+
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
405+
auto it = m_data.begin();
406+
std::advance(it, i);
407+
return *it;
408+
}
409+
410+
/**
411+
* @brief Access element at given index (non-const version)
412+
* @param i Index of the element to access
413+
* @return Reference to the element at index i
414+
* @note This overload is for when container does not support operator[] or mapped_type
415+
*/
416+
auto operator[](size_type i) -> decltype(auto)
417+
requires (!Type::HasSubscript<Container, size_type> && !Type::HasMappedType<Container>) {
418+
if (i >= m_data.size())
419+
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
420+
auto it = m_data.begin();
421+
std::advance(it, i);
422+
return *it;
423+
}
424+
425+
426+
/*
427+
* @brief Key-based access for associative containers (non-const)
428+
* @param key The key to access in the underlying container
429+
* @return Reference to the mapped value (forward to container's operator[])
430+
*/
431+
template<typename K>
432+
auto operator[](K const &key) -> decltype(auto)
433+
requires (Type::HasMappedType<Container>) {
434+
return m_data[static_cast<typename Container::key_type>(key)];
435+
}
436+
437+
/*
438+
* @brief Key-based access for associative containers (const)
439+
* @param key The key to access in the underlying container
440+
* @return Const reference to the mapped value (searches and throws if not found)
441+
*/
442+
template<typename K>
443+
auto operator[](K const &key) const -> decltype(auto)
444+
requires (Type::HasMappedType<const Container>) {
445+
auto k = static_cast<typename Container::key_type>(key);
446+
auto it = m_data.find(k);
447+
if (it == m_data.cend())
448+
throw OutOfBoundsError("Key not found in Iterable::operator[]");
449+
return it->second;
450+
}
451+
399452
/**
400453
* @brief Access element at given index (const version)
401454
* @param i Index of the element to access
402455
* @return Const reference to the element at index i
403456
*/
404-
const_reference operator[](size_type i) const {
457+
const_reference operator[](size_type i) const
458+
requires Type::HasSubscript<const Container, size_type> {
405459
if (i >= m_data.size())
406460
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
407461
return m_data[i];
408462
}
409463

464+
/**
465+
* @brief Access element at given index (const version)
466+
* @param i Index of the element to access
467+
* @return Const reference to the element at index i
468+
* @note This overload is for when container does not support operator[] or mapped_type
469+
*/
470+
const_reference operator[](size_type i) const
471+
requires (!Type::HasSubscript<const Container, size_type>) {
472+
if (i >= m_data.size())
473+
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
474+
auto it = m_data.cbegin();
475+
std::advance(it, i);
476+
return *it;
477+
}
478+
479+
/**
480+
* @brief Access element at given index (const version)
481+
* @param i Index of the element to access
482+
* @return Const reference to the element at index i
483+
* @note This overload is for when container does not support operator[] or mapped_type
484+
*/
485+
auto operator[](size_type i) const -> decltype(auto)
486+
requires (!Type::HasSubscript<const Container, size_type> && !Type::HasMappedType<const Container>) {
487+
if (i >= m_data.size())
488+
throw OutOfBoundsError("Index {} out of bounds in Iterable::operator[]", i);
489+
auto it = m_data.cbegin();
490+
std::advance(it, i);
491+
return *it;
492+
}
493+
494+
/**
495+
* @brief Adds an element to the container
496+
* @param value The element to add
497+
*/
498+
void add(const value_type& value) requires Type::HasPushBack<decltype(m_data)> {
499+
m_data.push_back(value);
500+
}
501+
410502
/**
411503
* @brief Adds an element to the container
412504
* @param value The element to add
413505
*/
414-
void add(const value_type& value) { m_data.push_back(value); }
506+
void add(const value_type& value) requires (!Type::HasPushBack<decltype(m_data)> and Type::HasPushFront<decltype(m_data)>) {
507+
m_data.push_front(value);
508+
}
509+
510+
/**
511+
* @brief Adds an element to associative containers via `insert`
512+
*/
513+
void add(const value_type& value) requires (!Type::HasPushBack<decltype(m_data)> and !Type::HasPushFront<decltype(m_data)> and Type::HasInsert<decltype(m_data)>) {
514+
m_data.insert(value);
515+
}
516+
517+
/**
518+
* @brief Adds an element to the container (move version)
519+
* @param value The element to add
520+
*/
521+
void add(value_type&& value) requires Type::HasPushBack<decltype(m_data)> {
522+
m_data.push_back(std::move(value));
523+
}
415524

416525
/**
417526
* @brief Adds an element to the container (move version)
418527
* @param value The element to add
419528
*/
420-
void add(value_type&& value) { m_data.push_back(std::move(value)); }
421-
};
529+
void add(value_type&& value) requires (!Type::HasPushBack<decltype(m_data)> and Type::HasPushFront<decltype(m_data)>) {
530+
m_data.push_front(std::move(value));
531+
}
532+
533+
/**
534+
* @brief Adds an element to associative containers via `insert` (move version)
535+
*/
536+
void add(value_type&& value) requires (!Type::HasPushBack<decltype(m_data)> and !Type::HasPushFront<decltype(m_data)> and Type::HasInsert<decltype(m_data)>) {
537+
m_data.insert(std::move(value));
538+
}
539+
540+
/**
541+
* @brief Checks if the container has a specific item
542+
* @param value The item to check for
543+
* @return True if the item exists in the container, false otherwise
544+
*/
545+
bool has_item(const value_type& value) const {
546+
for (const auto& item : m_data) {
547+
if (item == value) {
548+
return true;
549+
}
550+
}
551+
return false;
552+
}
553+
554+
template<typename M>
555+
bool has_item(M const &value) const
556+
requires Type::HasMappedType<const Container> && std::convertible_to<M, typename Container::mapped_type> {
557+
for (const auto &item : m_data) {
558+
if (item.second == value) return true;
559+
}
560+
return false;
561+
}
562+
563+
/**
564+
* @brief Checks if the container has a specific key (for associative containers)
565+
* @param key The key to check for
566+
* @return True if the key exists in the container, false otherwise
567+
*/
568+
template<typename K>
569+
bool has_key(const K& key) const
570+
requires Type::HasMappedType<const Container> {
571+
auto k = static_cast<typename Container::key_type>(key);
572+
return m_data.find(k) != m_data.cend();
573+
574+
}
575+
};
422576
}

lib/public/StormByte/type_traits.hxx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,66 @@ namespace StormByte {
9595
template<typename T>
9696
concept Container = is_container<T>::value;
9797

98+
/**
99+
* @brief Concept that checks whether a container supports `push_back`.
100+
*
101+
* Satisfied when the expression `c.push_back(v)` is valid where `v` is
102+
* convertible to the container's `value_type`.
103+
*/
104+
template<typename C>
105+
concept HasPushBack = Container<C> && requires(C& c, typename C::value_type const& v) {
106+
c.push_back(v);
107+
};
108+
109+
/**
110+
* @brief Concept that checks whether a container supports `push_front`.
111+
*/
112+
template<typename C>
113+
concept HasPushFront = Container<C> && requires(C& c, typename C::value_type const& v) {
114+
c.push_front(v);
115+
};
116+
117+
/**
118+
* @brief Concept that checks whether a container supports `insert` with a value.
119+
*/
120+
template<typename C>
121+
concept HasInsert = Container<C> && requires(C& c, typename C::value_type const& v) {
122+
c.insert(v);
123+
};
124+
125+
/**
126+
* @brief Concept that checks whether a container supports operator[] for a given key/index type `U`.
127+
*
128+
* Example: `HasSubscript<std::vector<int>, std::size_t>` is true, and
129+
* `HasSubscript<std::map<Key, T>, Key>` is also true because `std::map` provides `operator[]`.
130+
*/
131+
template<typename C, typename U>
132+
concept HasSubscript = Container<C> && requires(C& c, U const& u) {
133+
{ c[u] };
134+
};
135+
136+
/**
137+
* @brief Concept to check if a container has a `key_type` member type.
138+
* @tparam Container The container type to check.
139+
*
140+
* A type satisfies HasKeyType if it defines a `key_type` member type.
141+
* @code
142+
* template<Type::HasKeyType T>
143+
* void process(T container) { ... }
144+
* @endcode
145+
*/
146+
template<typename C>
147+
concept HasKeyType = Container<C> && requires { typename C::key_type; };
148+
149+
/**
150+
* @brief Concept to check if a container has a `mapped_type` member type.
151+
* @tparam C The container type to check.
152+
*
153+
* Typical associative containers (like `std::map`) provide a `mapped_type`.
154+
*/
155+
template<typename C>
156+
concept HasMappedType = Container<C> && requires { typename C::mapped_type; };
157+
98158
/**
99159
* @brief Concept to check if a type is an optional.
100160
* @tparam T The type to check.

0 commit comments

Comments
 (0)