nyarlathotep108
nyarlathotep108

Reputation: 5521

Type safe index values for std::vector

I have classes that collect index values from different constant STL vectors. Problem is, even if these vectors are different in content and they have different purposes, their indexes are of type std::size_t, so one might erroneusly use the index stored for one vector to access the elements of another vector. Can the code be changed in order to have a compile time error when a index is not used with the correct vector?

A code example:

#include <iostream>
#include <string>
#include <vector>

struct Named
{
    std::string name;
};

struct Cat : Named { };
struct Dog : Named { };

struct Range
{
    std::size_t start;
    std::size_t end;
};

struct AnimalHouse
{
    std::vector< Cat > cats;
    std::vector< Dog > dogs;
};

int main( )
{
    AnimalHouse house;
    Range cat_with_name_starting_with_a;
    Range dogs_with_name_starting_with_b;

    // ...some initialization code here...

    for( auto i = cat_with_name_starting_with_a.start;
         i < cat_with_name_starting_with_a.end;
         ++i )
    {
        std::cout << house.cats[ i ].name << std::endl;
    }

    for( auto i = dogs_with_name_starting_with_b.start;
         i < dogs_with_name_starting_with_b.end;
         ++i )
    {
        // bad copy paste but no compilation error
        std::cout << house.cats[ i ].name << std::endl; 
    }

    return 0;
}

Disclaimer: please do not focus too much on the example itself, I know it is dumb, it is just to get the idea.

Upvotes: 1

Views: 659

Answers (1)

super
super

Reputation: 12978

Here is an attempt following up on my comment.
There are of course a lot of room to change the details of how this would work depending on the use-case, this way seemed reasonable to me.

#include <iostream>
#include <vector>

template <typename T>
struct Range {
    Range(T& vec, std::size_t start, std::size_t end) :
        m_vector(vec),
        m_start(start),
        m_end(end),
        m_size(end-start+1) {}

    auto begin() {
        auto it = m_vector.begin();
        std::advance(it, m_start);
        return it;
    }
    auto end() {
        auto it = m_vector.begin();
        std::advance(it, m_end + 1);
        return it;
    }

    std::size_t size() {
        return m_size;
    }

    void update(std::size_t start, std::size_t end) {
        m_start = start;
        m_end = end;
        m_size = end - start + 1;
    }

    Range copy(T& other_vec) {
        return Range(other_vec, m_start, m_end);
    }

    typename T::reference operator[](std::size_t index) {
        return m_vector[m_start + index];
    }

    private:
    T& m_vector;
    std::size_t m_start, m_end, m_size;
};

// This can be used if c++17 is not supported, to avoid
// having to specify template parameters
template <typename T>
Range<T> make_range(T& t, std::size_t start, std::size_t end) {
    return Range<T>(t, start, end);
}

int main() {
    std::vector<int> v1 {1, 2, 3, 4, 5};
    std::vector<double> v2 {0.5, 1., 1.5, 2., 2.5};

    Range more_then_2(v1, 1, 4); // Only works in c++17 or later
    auto more_then_1 = make_range(v2, 2, 4);

    for (auto v : more_then_2)
        std::cout << v << ' ';

    std::cout << std::endl;

    for (auto v : more_then_1)
        std::cout << v << ' ';

    std::cout << std::endl;

    more_then_2.update(2,4);

    for (auto v : more_then_2)
        std::cout << v << ' ';

    std::cout << std::endl;

    auto v3 = v1;
    auto more_then_2_copy = more_then_2.copy(v3);

    for (unsigned i=0; i < more_then_2_copy.size(); ++i)
        std::cout << more_then_2_copy[i] << ' ';

    return 0;
}

Upvotes: 2

Related Questions