'Handling custom vector classes

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?


Solution 1:[1]

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 a size_t because that's idiomatic but if you'd like to keep it as an int, that's fine too.
  • std::next(m_Items.begin(), index) will do the same as m_Items.begin() + index, but in cases where the iterator returned by m_Items.begin() is a plain pointer, using std::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 in const contexts too.
    template<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';
        }
    }
    
    None of the three member functions used above could be used without the const qualified overloads.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1