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
.