Home > Blockchain >  Better way to give constant view of pointers in C
Better way to give constant view of pointers in C

Time:07-09

I have a class that must return a constant view of some pointers to the upper layers of software.

Internally, the pointers must be non-const, because the class will need to manipulate the objects internally.

I don't see any option for providing this const view of the pointers to a higher level client, without making a copy of all the pointers. This seems wasteful. What if I was managing millions of objects?

Is there a better way?

Here is some example code:

#include <vector>
#include <iostream>

class example {
public:
  
  example() {
    bytePtrs_.push_back(new char);
    *bytePtrs_[0] = '$';
  }

  // I want to do this, but compiler will not allow
  // error: could not convert ‘((example*)this)->example::bytePtrs_’ from ‘std::vector<char*>’ to ‘std::vector<const char*>’
  std::vector<const char*> getPtrs() { 
    return bytePtrs_; 
  }

  // Must make wasteful copy
  std::vector<const char*> getPtrs() { 
    std::vector<const char*> ret;
    for (auto &ptr : bytePtrs_)
      ret.push_back(ptr);
    return ret; 
  }

private:

  std::vector<char*> bytePtrs_;
};

int main() {

  example e;

  std::vector<const char*> bytePtrs = e.getPtrs();

  std::cout << bytePtrs[0] << std::endl; 

}

CodePudding user response:

You can do this using std::experimental::propagate_const.

That will forward the constness of the pointer onto the pointed-to object.

#include <experimental/propagate_const>

class example {
public:

//  using vector = std::vector<char*>>;
    using vector = std::vector<std::experimental::propagate_const<char*>>;

    example() {
        bytePtrs.push_back(new char);
        *bytePtrs[0] = '$';
    }

    vector const& getPtrs() const {
        return bytePtrs;
    }

private:

    vector bytePtrs;
};

int main()
{
    example e;

    example::vector const& bytePtrs = e.getPtrs();

    std::cout << bytePtrs[0] << std::endl; // fine and dandy

    *bytePtrs[0] = 'x'; // compile error
}

CodePudding user response:

Consider returning a proxy object when you only want to permit const access, something like this (edited to fix the massive hole in the original code pointed out by @alagner!):

#include <iostream>
#include <vector>

template <class T> class proxy_vector
{
public:
    proxy_vector (const std::vector<T *> &v) : m_v (v) { }
    size_t size () { return m_v.size (); }
    const T * const &operator[] (size_t i) const { return m_v [i]; }
    // ... more functions as needed
private:
    const std::vector <T *> &m_v;
};

class example
{
public:
    example() : m_pv (bytePtrs_)
    {
        bytePtrs_.push_back(new char);
        *bytePtrs_[0] = '$';
    }

    const proxy_vector <char> &getPtrs() { return m_pv; }

private:
    std::vector<char*> bytePtrs_;
    proxy_vector <char> m_pv;
};

int main()
{
    example e;
    auto &bytePtrs = e.getPtrs ();
//  *bytePtrs [0] = 'x';    // uncomment to show that this code now actually works as intended!
    std::cout << bytePtrs[0] << "\n";
}

A decent compiler should optimise most, if not all, of this away. Add access methods to proxy vector as needed, I doubt you will need many.

Demo (seems to work fine in C 11).


What @alagner suggests should also work and might be simpler. I haven't thought that through.

CodePudding user response:

As the OP has pointed out in one of the comments, this is the API the code needs to comply to: https://github.com/Xilinx/Vitis-AI/blob/master/src/Vitis-AI-Runtime/VART/vart/runner/include/vart/runner.hpp#L157

So by design it returns by copy, which is unavoidable really, thus what really can be done is this:

std::vector<const char*> getPtrsToConst()
{
  std::vector<char*> x;
  //fill the vector somehow
  return std::vector<const char*>(x.begin(), x.end());
}

If the return value should be created and preallocated upfront is another topic and it's hard to tell without measuring.

CodePudding user response:

This is one of the very few cases where reinterpret_cast<> could be allowed into the code I think: since the std::vector<char *> and const std::vector<const char*>& are assumed to be compatible in memory anyways, you might cast. Thus, getPtrs() could actually return reinterpret_cast<const std::vector<const char*>&>(bytePtrs_);. To be clear: this is an optimization that relies on concrete ABI details (ItaniumABI et. al.), not generic standard C , but I'm not yet aware of any architecture where it wouldn't work.

In c 20 and later, you might consider checking std::ranges::view.

  •  Tags:  
  • c
  • Related