I have come across many occasions where I want to have an item which is selected inside a vector, for this I have written the template class:
// a vector wrapper which allows a specific item to be currently selected
template<typename T>
class VectorSelectable
{
public:
VectorSelectable() {};
VectorSelectable(std::initializer_list<T> items) : m_Items(items) {};
void Add(const T& v) { m_Items.push_back(v); m_CurrentIndex = m_Items.size()-1; } // lvalue & refs
void Add(T&& v) { m_Items.push_back(std::move(v)); m_CurrentIndex = m_Items.size()-1; } // rvalue
void Remove(size_t index) {
assert(index < m_Items.size());
m_Items.erase(m_Items.begin() index);
if(m_CurrentIndex != -1 && (int)index <= m_CurrentIndex)
m_CurrentIndex--;
}
void RemoveCurrent() { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); Remove(m_CurrentIndex); }
T& CurrentItem() { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); return m_Items[m_CurrentIndex]; }
T& operator [](size_t index) { assert(index < Size()); return m_Items[index]; }
// moves value of n_next onto n, and n_new onto n
void ItemSwap(size_t n, size_t n_Next) {
assert(n < m_Items.size());
assert(n_Next < m_Items.size());
T itemBuf = std::move(m_Items[n]);
m_Items[n] = m_Items[n_Next];
m_Items[n_Next] = std::move(itemBuf);
}
size_t Size() { return m_Items.size(); }
const std::vector<T>& Data() { return m_Items; }
std::vector<T>* DataPtr() { return &m_Items; }
T* ItemPtr(size_t index) { assert(index < m_Items.size()); return &m_Items[index]; }
void SetCurrentIndex(int index) { assert(index >= -1 && index < (int)m_Items.size()); m_CurrentIndex = index; }
int& CurrentIndex() { return m_CurrentIndex; }
bool HasItemSelected() { return m_CurrentIndex != -1; }
private:
std::vector<T> m_Items;
int m_CurrentIndex = -1;
};
I am also coming across many scenarios where I want a vector of unique_ptrs (generally for polymorphic classes), this looks like this:
template<typename T>
class Vector_UniquePtrs
{
public:
// Adds an Item (and returns a raw ptr to it)
// usage: v.Add() ... (equivelent to v.Add<base_class>())
template<typename... Args>
T* Add(Args... args) {
return Add<T>(args...);
}
// Adds a Polymorphic Item (and returns a raw ptr to it)
// usage: v.Add<sub_class>()
template<typename T2, typename... Args>
T* Add(Args... args) {
m_Items.push_back(std::unique_ptr<T>(new T2(args...)));
return m_Items.back().get();
}
// Remove Item
void Remove(size_t index) {
assert(index < m_Items.size());
m_Items.erase(m_Items.begin() index);
}
T* operator [](size_t index) { assert(index < Size()); return m_Items[index].get(); }
size_t Size() { return m_Items.size(); }
private:
std::vector<std::unique_ptr<T>> m_Items;
};
My question is:
- How can I handle a combination of these 2 class types (e.g. VectorSelectable<unique_ptr>) as one returns ptrs, the other returns references, is the only option to write an entirely new class?
CodePudding user response:
You mainly need to put the std::vector<std::unique_ptr<T>>
in VectorSelectable
and hide all the pointer stuff from the interface. With a few small changes to your class, it could look like this:
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
template <typename T>
class VectorPtrSelectable {
public:
VectorPtrSelectable() = default;
VectorPtrSelectable(std::initializer_list<T> items) :
m_CurrentIndex(items.size() - 1)
{
m_Items.reserve(items.size());
// fill `m_Items` from the initializer list ...
std::transform(items.begin(), items.end(), std::back_inserter(m_Items),
[](const T& item) {
// ... by creating a unique_ptr from each element (transformation)
return std::make_unique<T>(item);
});
};
template <class U, class... Args>
T& Add(Args&&... args) {
// make `Add` forward to `make_unique`
m_Items.emplace_back(std::make_unique<U>(std::forward<Args>(args)...));
m_CurrentIndex = m_Items.size() - 1;
// and return a reference instead
return *m_Items.back();
}
template <class... Args>
T& Add(Args&&... args) {
// forward to Add<U>
return Add<T>(std::forward<Args>(args)...);
}
void Remove(size_t index) {
m_Items.erase(std::next(m_Items.begin(), index));
if (m_CurrentIndex != static_cast<size_t>(-1) && index <= m_CurrentIndex)
m_CurrentIndex--;
}
T& operator[](size_t index) { return *m_Items[index]; }
const T& operator[](size_t index) const { return *m_Items[index]; }
T& CurrentItem() { return *m_Items[m_CurrentIndex]; }
const T& CurrentItem() const { return *m_Items[m_CurrentIndex]; }
void SetCurrentIndex(size_t index) { m_CurrentIndex = index; }
void RemoveCurrent() { Remove(m_CurrentIndex); }
bool HasItemSelected() { return m_CurrentIndex != static_cast<size_t>(-1); }
void ItemSwap(size_t n, size_t n_Next) {
// simplified swapping:
std::swap(m_Items[n], m_Items[n_Next]);
}
// make functions that does not change your instance const qualified:
size_t CurrentIndex() const { return m_CurrentIndex; }
size_t Size() const { return m_Items.size(); }
private:
std::vector<std::unique_ptr<T>> m_Items;
size_t m_CurrentIndex = static_cast<size_t>(-1); // size_t for the index
};
Example usage:
#include <iostream>
#include <string>
int main() {
VectorPtrSelectable<std::string> vs{"World", "Hello"};
std::cout << vs.CurrentItem() << '\n';
vs.ItemSwap(0, 1);
std::cout << vs.CurrentItem() << '\n';
vs.RemoveCurrent();
std::cout << vs.CurrentItem() << '\n';
std::cout << vs.Add("Add and get a reference") << '\n';
}
Output:
Hello
World
Hello
Add and get a reference
- I made
m_CurrentIndex
asize_t
because that's idiomatic but if you'd like to keep it as anint
, that's fine too. std::next(m_Items.begin(), index)
will do the same asm_Items.begin() index
, but in cases where the iterator returned bym_Items.begin()
is a plain pointer, usingstd::next
avoids potential warnings about using pointer arithmetic.- Returning a reference instead of a pointer to the added element makes no difference other than making the interface more idiomatic. It's simply what a user of the class is likely to expect. Returning a pointer also opens up questions like "can it return
nullptr?
" etc. - The added
const
qualified functions makes those functions usable inconst
contexts too.
None of the three member functions used above could be used without thetemplate<class T> void foo(const VectorPtrSelectable<T>& vps) { // note: const& if(vps.Size() > 0) { std::cout << "the first element is " << vps[0] << '\n'; std::cout << "the current element is " << vps.CurrentItem() << '\n'; } }
const
qualified overloads.