Search code examples
c++arraysiteratorstatic-cast

Trying to implement begin/end with static_cast in C++


I'm trying to implement an object to iterate easily an array, without static_cast each element separately (I have to do it hundreds of times). It works when I use the classic for loop, but when I attempt to use begin()/data()/end() it fails to compile. Do you know where is the mistake?

If I have these two classes:

struct Wrapper
{
   int var1 = 0;
};

struct WrapperBig: public Wrapper
{
   int var2 = 0;
};

And this is the object which will cast the pointers:

template <class T, int N>
class ArrayCasted final
{
public:
   ArrayCasted(std::array<Wrapper*, N>& p): master(p) {}

   // Works
   int size() const {return static_cast<int>(master.size());}
   inline T* operator[](const int index) {return static_cast<T*>(master[index]);}

   // Fails
   inline T* begin() const noexcept {return static_cast<T**>(master.begin());}
   inline T* end() const noexcept {return static_cast<T**>(master.end());}
   inline T* data() const noexcept {return static_cast<T**>(master.data());}

private:
   std::array<Wrapper*, N>& master;
};

When I try to compile in Visual Studio 2019:

   Wrapper* w1 = new Wrapper();
   std::array<Wrapper*, 2> values {w1, w1};

   ArrayCasted<WrapperBig, 2> casted {values};

   // Works!
   for (int i=0; i<casted.size(); ++i)
   {
      WrapperBig* current = casted[i];
      current->var2 = -1;
   }

   // Fails :( C2440
   for (WrapperBig* current : casted)
   {
      current->var2 = -1;
   }

I tried the same with std::vector, but same problem. I think the mistake is in the begin/data/end functions, but I'm not sure why. I tried multiple things but everything fails.

How can I do this?


Solution

  • This code has a number of problems.

    1. You've gotten your levels of indirection confused a bit.
    2. The return from someArray.begin() is an iterator, which may be a pointer, but may instead be something else entirely, in which casting it to a pointer will completely fail.
    3. If you start with an array<Wrapper *, 2>, you probably want the ArrayCasted to be to a array<WrapperBig *, 2>.
    4. If you're going to use the contents as pointer to WrapperBig, you need to assure that what they point at are actually WrapperBig objects.

    Putting those together we could get something on this order:

    template <class T, int N>
    class ArrayCasted final
    {
    public:
       ArrayCasted(std::array<Wrapper*, N>& p): master(p) {}
    
       // Works
       int size() const {return static_cast<int>(master.size());}
       inline T* operator[](const int index) {return static_cast<T*>(master[index]);}
    
       // These work
       // start from a pointer, cast it to the right kind of pointer:
       inline T* begin() const noexcept {return reinterpret_cast<T*>(master.data());}
       inline T* end() const noexcept {return reinterpret_cast<T*>(master.data() + master.size());}
       inline T* data() const noexcept {return reinterpret_cast<T*>(master.data());}
    
    private:
       std::array<Wrapper*, N>& master;
    };
    
    int main() {
    
       std::array<Wrapper*, 2> values;
    
       // make sure each actually points at WrapperBig object:
       for (auto & w : values) {
            w = new WrapperBig;
       }
    
       ArrayCasted<WrapperBig *, 2> casted {values};
    
       // Doesn't Fail
       int i = -1;
       for (WrapperBig* current : casted)
       {
          current->var2 = i++;
       }
    
        // auto properly deduces the type too:
        for (auto c : casted) {
            // When you type `c->`, Intellisense knows what comes next has to
            // be either `var1` or `var2`.
            std::cout << c->var2 << "\n";
        }
    }