I'm playing with C++ and const-correctness right now.
Assume you have the following structure
template <typename T>
struct important_structure {
public:
T* data;
int a;
important_structure(const T& el, int a);
void change();
};
template <typename T>
void important_structure<T>::change() {
//alter data field in some way
}
template <typename T>
important_structure <T>::important_structure(const T& el, int a) : data(&el), a(a) //error line {
};
int main() {
important_structure<int>* s = new important_structure<int>{5, 3};
}
When compiling with std=c++11, the compiler returns the following error:
invalid conversion from ‘const int*’ to ‘int*’
Now, I know it's unsafe to cast a const int* to int*. The problem is that I have a data structure and I don't want to put the field data as a constant.
However, I don't want to remove the const qualifier in the constructor since, I think, it's informative for future developers: it clearly says that el won't be modified by the function. Still the field data may be modified by some other function in important_structure.
My question is: How can I deal with fields which are initialized in the costructor and altered in some other function?
Most of const correctness deals with simple answers, but no question (I think) deals with scenarios where a const parameter is passed to a data structure and then such data structure is altered by someone else.
Thanks for any kind reply
passing el as a const reference doesn't just mean the function will not change el during the run of the function, it means because of this function call, el won't be changed at all. And by putting the address of el into non-const data, you violate that promise.
So, the clean solution, if you indeed want to change data, is removing the const. since it is not informative to future developers, but misleading. Casting away the const would be very bad here.
Let's use a simple class as T type of important_struct:
class Data
{
public:
Data() : something(0){}
Data(int i) : something(i){}
Data(const Data & d) : something(d.something){}
//non-const method: something can be modified
void changeSomething(int s){ something += s; }
//const method: something is read-only
int readSomething() const { return something; }
private:
int something;
};
This class has a very simple, yet well encapsulated, status, i.e. the int something field, which is accessed through methods in a very controlled way.
Let (a simplified version of) important_structure hold an instance of Data as a private field:
template <typename T>
struct important_structure
{
public:
important_structure(T * el);
void change();
int read() const;
private:
T* data;
};
We can assign a Data instance to an important_structure instance this way:
important_structure<Data> s(new Data());
The instance is assigned in construction:
template <typename T>
important_structure <T>::important_structure(T * el) : data(el) {}
Now the great question: do important_structure take ownership of the Data instances it holds? The answer must be made clear in documentation.
If it is yes, important_structure must take care of memory cleanup, e.g. a destructor like this one is required:
template<typename T>
important_structure<T>::~important_structure()
{
delete data;
}
Notice that, in this case:
Data * p = new Data()
// ...
important_structure<Data> s(p);
//p is left around ...
another pointer to the Data istance is left around. What if someone mistakenly call delete on it? Or, even worse:
Data d;
// ...
important_structure<Data> s(&p); //ouch
A much better design would let important_structure own its own Data instance :
template <typename T>
struct important_structure
{
public:
important_structure();
void change();
// etc ...
private:
T data; //the instance
};
but this is maybe simplistic or just unwanted.
One could let important_structure copy the instance it will own:
template<typename T>
important_structure<T>::important_structure(const T &el)
{
data = el;
}
the latter being the constructor provided in the question: the object passed won't be touched, but copied. Obviously, there are two identical Data objects around, now. Again, the result could not be what we needed in the first place.
There is a third way, in the middle: the object is instantiated outside the owner, and moved to it, using move semantics.
As an example, let's give Data a move assignment operator:
Data & operator=(Data && d)
{
this->something = d.something;
d.something = 0;
return *this;
}
and let important_structure provide a constructor which accepts an rvalue reference of T:
important_structure(T && el)
{
data = std::move(el);
}
One can still pass a Data instance using a temporary as the required rvalue:
important_structure<Data> s(Data(42));
or an existing one, providing the required reference from an lvalue, thanks to std::move:
Data d(42);
// ...
important_structure<Data> x(std::move(d));
std::cout << "X: " << x.read() << std::endl;
std::cout << "D: " << d.readSomething() << std::endl;
In this second example, the copy held by important_structure is considered the good one while the other is left in a valid but unspecified state, just to follow the standard library habits.
This pattern is, IMHO, more clearly stated right in code, expecially if considered that this code will not compile:
Data d(42);
important_structure<Data> x (d);
Whoever wants an instance of important_structure must provide a temporary Data instance or explicitly move an existing one with std::move.
Now, let the important_structure class be a container, as you asked in comment, so that data is somehow accessible from outside. Let's give a method like this to the important_structure class:
const T & owneddata() { return data; }
Now, we can use data const methods like this:
important_structure<Data> s(Data(42));
std::cout << s.owneddata().readSomething() << std::endl;
but calls to `Data' non-const methods will not compile:
s.owneddata().changeSomething(1000); //not compiling ...
If in need of it (hope not), expose a non-const reference:
T & writablereference() { return data; }
Now the data field is at full disposal:
s.writablereference().changeSomething(1000); //non-const method called
std::cout << s.owneddata().readSomething() << std::endl;
Using const T& el and data(&el) is a really bad idea, because it implies that you could write:
new important_structure<int>{5, 3};
But to write new important_structure<int>{5, 3}; would result in data holding an address that would no longer be valid immediately after calling the constructor.
If you want that the point data can be changed, but that the value where the pointer points to cannot be changed, then you want to write it that way:
template <typename T>
struct important_structure {
public:
T const * data;
int a;
important_structure(T const * el, int a);
void change();
};
template <typename T>
void important_structure<T>::change() {
//alter data field in some way
}
template <typename T>
important_structure <T>::important_structure( T const * el, int a) : data(el), a(a) { //error line
};
int main() {
int i = 5;
important_structure<int>* s = new important_structure<int>{&i, 3};
}
Related
I have two classes,
template<class Type>
class SafePtr {
public:
SafePtr() {}
~SafePtr() {}
void Lock(Type* data, void* key)
{
if (!pKey)
{
pKey = key;
pData = data;
}
}
Type* Unlock(void* key) const
{
if (key == pKey)
return pData;
}
Type* operator->()
{
return pData;
}
private:
Type* pData = nullptr;
void* pKey = nullptr;
};
template<class Type>
class SafePtrArray {
public:
SafePtrArray() {}
~SafePtrArray() {}
template<class... Args>
SafePtr<Type> CreatePtr(Args&&... args)
{
Type* data = new Type(args...);
ptrs.insert(ptrs.end(), data);
SafePtr<Type> ptr;
ptr.Lock(data, this);
return ptr;
}
Type* UnlockPtr(const SafePtr<int>& ptr)
{
return ptr.Unlock(this);
}
void Destroy(const SafePtr<int>& ptr)
{
Type* pointer = ptr.Unlock(this);
for (auto itr = ptrs.begin(); itr != ptrs.end(); itr++)
{
if ((*itr) == pointer)
{
delete pointer;
ptrs.erase(itr);
}
}
}
private:
std::vector<Type*> ptrs;
};
The goal is to protect a pointer so that the user can access its members but not get to manipulate its actual pointer (mainly delete it prematurely). And also I need to store all the pointers in an array so that when the parent object destroys, I can automatically destroy all the allocated pointers
For this I use two classes, SafePtr and SafePtrArray. SafePtrArray creates and stores the pointers and wraps them in the SafePtr and returns it to the user. SafePtr is just a wrapper and should not let the user get access to the underlying pointer but will allow them to access its members.
It works fine at first but soon I found this error,
int main()
{
SafePtrArray<int> ptr;
auto pInt = ptr.CreatePtr();
int* i = pInt.operator->(); // Users can get access to the underlying pointer using this.
ptr.Destroy(pInt);
}
Is there a way to prevent users from getting access to the underlying type and prevent them from manipulating the pointer while having the privilege to access its members?
I still think you try to solve a problem that has more to do with possible flaws in the design of the API/of the code, the documentation, or with the lack of C++ knowledge of the one using it, with a "solution" that has more cons than pros.
If a C++ programmer does not know what ownership is or does not respect it and blindly deletes objects or frees the memory of pointers, then there will be much bigger concerns. You likely will move the problem just to a different part of the code.
Having that said, the closest you can do to not expose the pointer right now is something like this:
(The code is just a proof of concept, so things like call might need to be improved)
#include <iostream>
#include <string>
struct Test {
void foo(int x, int y, std::string str) {
std::cout << x << " " << y << " " << str << std::endl;
}
double test = 0.5;
};
template <typename T>
struct Ptr {
template <auto M, typename... Args>
auto call(Args... args) {
return (obj.*M)(std::forward<Args>(args)...);
}
template <auto M>
auto get() {
return (obj.*M);
}
protected:
T obj;
};
int main() {
Ptr<Test> p;
p.call<&Test::foo>(1, 2, "hello");
std::cout << p.get<&Test::test>() << std::endl;
return 0;
}
But I still don't think that this is a good approach.
And a user can still mess around with the code and do something bad like:
int main() {
Ptr<Test> p;
delete &p;
return 0;
}
Or this, which for sure is undefined behavior, but that does not really matter as deleting a not owned object will also result in undefined behavior at some point:
template<typename T>
struct Ptr {
protected:
T *obj;
}
template<typename T>
struct Ptr2 {
public:
T *obj;
};
int main()
{
Ptr<Test> p;
Ptr2<Test> *p2 = reinterpret_cast<Ptr2<Test>*>(&p);
std::cout << p2->obj << std::endl;
}
So there is no protection again such things.
Besides the shown code, there is a proposal for reflection that is feature complete now, which would allow getting information about the members of a type, but this was not added to c++20, and one for metaclasses which is also not in the standard yet.
With these two proposals, you might be able to implement something better useable. But my concerns about the benefits of this remain.
Is there a way to prevent users from getting access to the underlying type and prevent them from manipulating the pointer while having the privilege to access its members?
Under certain conditions, no, this is not possible. If the underlying Type is a standard layout class then providing access to the first non-static non-bitfield data member breaks your goal. (Caveat: providing access to just the value of the member is a different story.) The address of that member can be converted to a pointer to the underlying object via reinterpret_cast, which allows calling delete on that pointer. (Well, "allows" in the sense that the call is syntactically valid. Not much else matters for "allows" since we are headed into undefined behavior anyway.)
For classes that are not standard layout, there are probably compiler-specific (non-portable) methods to achieve the same effect (converting the address of a data member into a pointer to the underlying object). There is no reason for a compiler to try actively to thwart such things.
If a programmer is determined to invoke undefined behavior, there is little you can do to stop it.
This question is based on the example code below, which is inspired by Sean Parent's talk.
The goal of the code below is to provide an object wrapper similar to boost::any. I wrote this code to educate myself of type erasure. So, there is no practical uses this code intends (considering there is already boost::any).
class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
friend typename T * getObjPtr(ObjWrap O) {
return static_cast<T*>(O.Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};
Before I can really ask my question, let's examine the code in the following aspects:
Concept::getObjPtr_ returns void* because a) Concept cannot be a template otherwise unique_ptr<Concept> Self would not work; b) void* is the only way I know how to return Obj::Data in a type-agnostic way in C++. Please correct me if this is wrong...
T * getObjPtr(ObjWrap O) is a template that needs instantiation separately from the ObjWrap constructor.
The use of ObjWrap basically includes: a) make a new ObjWrap over an existing object; b) retrieve the underlying object given an ObjWrap. For example:
ObjWrap a(1);
ObjWrap b(std::string("b"));
int* p_a = getObjPtr<int>(a);
std::string* p_b = getObjPtr<std::string>(b);
This works but it is obvious that getObjPtr<int>(b) does not work as intended.
So, my question is:
Is there a way to fix the above code so that we can simply use int* p_a = getObjPtr(a) and std::string* p_b = getObjPtr(b) or better yet auto p_a = getObjPtr(a) and auto p_b = getObjPtr(b)? In other words, is there a way in C++ to instantiate two templates at the same time (if so, we can instantiate the ObjWrap constructor and T* getObjPtr(ObjWrap) at compile time of a ObjWrap object, e.g., ObjWrap a(1))?
Edit 1:
Making ObjWrap a templated class does not help since it defeats the purpose of type erasure.
template <typename T>
class ObjWrap {
/* ... */
};
ObjWrap<int> a(1); // this is no good for type erasure.
Edit 2:
I was reading the code and realize that it can be modified to reflect the idea a little better. So, please also look at the following code:
class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
T * getObjPtr() {
return static_cast<T*>(Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};
int main() {
ObjWrap a(1);
ObjWrap b(std::string("b"));
int* p_a = a.getObjPtr<int>();
std::string* p_b = b.getObjPtr<std::string>();
std::cout << *p_a << " " << *p_b << "\n";
return 0;
}
The main difference between this version of the code versus the one above is that T * getObjPtr() is a member function that is encapsulated by the ObjWrap object.
Edit 3:
My question regarding type erasure is answered by accepted answer. However, the question on simultaneous type instantiation to multiple templates is yet to be answered. My guess is currently C++ does not allow it but it would be nice to hear from people with more experience on that.
There are a few things that may help.
First thing to say is that if Obj ever needs to expose the address of the object, it's not Sean Parent's 'inheritance is the root of all evil' type-erasing container.
The trick is to ensure that the interface of Obj offers all semantic actions and queries the wrapper will ever need.
In order to provide this, it's often a reasonable idea to cache the address of the object and its type_id in the concept.
Consider the following updated example, in which there is one public method - operator==. The rule is that two Objs are equal if they contain the same type of object and those objects compare equal.
Note that the address and type_id:
1) are implementation details and not exposed on the interface of Obj
2) are accessible without virtual calls, which short-circuits the not-equal case.
#include <memory>
#include <utility>
#include <typeinfo>
#include <utility>
#include <cassert>
#include <iostream>
class ObjWrap
{
public:
template <typename T>
ObjWrap(T O) : Self(new Model<T>(std::move(O))) {}
// objects are equal if they contain the same type of model
// and the models compare equal
bool operator==(ObjWrap const& other) const
{
// note the short-circuit when the types are not the same
// this means is_equal can guarantee that the address can be cast
// without a further check
return Self->info == other.Self->info
&& Self->is_equal(other.Self->addr);
}
bool operator!=(ObjWrap const& other) const
{
return !(*this == other);
}
friend std::ostream& operator<<(std::ostream& os, ObjWrap const& o)
{
return o.Self->emit(os);
}
private:
struct Concept
{
// cache the address and type here in the concept.
void* addr;
std::type_info const& info;
Concept(void* address, std::type_info const& info)
: addr(address)
, info(info)
{}
virtual ~Concept() = default;
// this is the concept's interface
virtual bool is_equal(void const* other_address) const = 0;
virtual std::ostream& emit(std::ostream& os) const = 0;
};
template <typename T>
struct Model : Concept
{
Model(T O)
: Concept(std::addressof(Data), typeid(T))
, Data(std::move(O)) {}
// no need to check the pointer before casting it.
// Obj takes care of that
/// #pre other_address is a valid pointer to a T
bool is_equal(void const* other_address) const override
{
return Data == *(static_cast<T const*>(other_address));
}
std::ostream& emit(std::ostream& os) const override
{
return os << Data;
}
T Data;
};
std::unique_ptr<Concept> Self;
};
int main()
{
auto x = ObjWrap(std::string("foo"));
auto y = ObjWrap(std::string("foo"));
auto z = ObjWrap(int(2));
assert(x == y);
assert(y != z);
std::cout << x << " " << y << " " << z << std::endl;
}
http://coliru.stacked-crooked.com/a/dcece2a824a42948
(etc. etc.) Please correct me if this is wrong...
Your premise is wrong at least in principle, if not also in practice. You're insisting on making getObjPtr() a virtual method, and using an abstract base class. But - you've not established this is necessary. Remember - using virtual methods is expensive! Why should I pay for virtuals just to get type erasure?
Is there a way to fix the above code so that we can simply use int* p_a = getObjPtr(a)
Take Sean Parent's talk title to heart (as opposed to the fact that he does use inheritance in the talk), drop the inheritance and the answer should be Yes. Edit: It's sufficient for the code that erases the type and the code that un-erases the type to know what the type is - as long as you don't need to act on the type-erased data in a type-specific way. In Sean Parent's talk, you need to be able to make non-trivial copies of it, to move it, to draw it etc. With std::any/boost::any you might need copying and moving, which may require virtuals - but that's the most general use case.
Even std::any limits what you can and can't do, as is discussed in this question:
why doesn't std::any_cast support implicit conversion?
I want to write a function which:
Takes a pointer as a parameter
Takes a length as a parameter
Owns the memory pointed to by the pointer (e.g. maybe it releases it, or constructs a unique_ptr to it in some data structure etc.)
Now, if I wanted 1+2 I would just use gsl::span. And if wanted 1+3 I would use owner<T*>. But what should I do when I want all three? Should I pass an owner<gsl::span<T>>? Something else?
Notes:
You may not assume the pointer is into the heap.
std::vector is requiring too much. The function should not require the caller to construct an std::vector.
One option would be to define your own abstract base class to encapsulate the data. Something like:
template<typename T>
class DataHolder {
public:
virtual ~DataHolder() = default;
virtual gsl::span<T> get() const = 0;
};
Then your function could look something like:
void foo(std::unique_ptr<DataHolder<int>> data) {
if (!data)
return;
for (auto v : data->get())
std::cout << v << " ";
}
The caller can then implement the base class with any container they want to. There will be a small cost of polymophism but not on a per-element basis.
If you don't want to pay for polymorphism, perhaps you could make your function accept a template parameter.
template<typename DataHolder>
void foo(DataHolder data) {
for (auto v : data())
std::cout << v << " ";
}
where the implicit interface for DataHolder could be satisfied by something like:
struct VectorHolder {
std::vector<int> data;
gsl::span<const int> operator()() const { return data; }
};
or if you really don't want to use vector. You could use something like this (as suggested by #utnapistim):
struct ArrayHolder {
std::unique_ptr<int[]> data;
ptrdiff_t length;
gsl::span<const int> operator()() const { return {data.get(), length}; }
};
I have to write a generic data structure that resembles a C++ vector as part of an assignment.
This is my idea for the Vector:
template<typename T>
class MyVector {
private:
T* data_;
size_t size_;
public:
MyVector();
MyVector(const MyVector &otherVector);
// which one should I use?
add(const T& value);
add(T value);
~MyVector();
};
Now I wonder how to pass values to the methods. Coming from Java I am a bit overwhelmed. In Java you wouldn't hesitate and pass the value by reference, the GC would never delete the object if it is still referenced.
In C++ you would create a mess if you would pass by reference considering code like this:
void myFunction(MyVector &myVector) {
int a = 5;
myVector.add(a);
}
int main() {
auto vector = MyVector<int>();
myFunction(vector);
// now the vector contains a reference to
// something that doesn't exist anymore.
}
How do you solve this problem? Would you just pass by reference and create a copy or do you pass by value (which creates a copy for you)
Looking at the C++ std::vector interface I see that they use references.
I just don't see the value of passing by reference if you have to create your own copy.
add(const T& value) is ok, you just should be sure that there is properly defined assign operator for T. So, the implementation will be:
void Add(const T& value) {
if (m_size == m_maxSize) realloc(); // stuff to have enough space
m_data[m_size++] = value; // here copy is creating
}
default impl of assign operator just byte-copy fields of class, it is not always correct.
Other solution, if you want more java-style semantic, is to make T = shared_ptr<YourType> or T = YourType*
The latter is rather difficult because require skill of manual lifetime control, so is undesirable for c++ beginners.
void myFunction(MyVector<shared_ptr<X>> & myVector)
{
shared_ptr<X> x(new X(...));
myVector.add(x);
}
works similar to references in Java.
Other way, that was used in old times:
template<typename T>
class MyVector {
private:
T** data_; // now you have array of pointers, so should be careful
....
add(T* value);
....
}
void myFunction(MyVector<X> & myVector)
{
X * x = new X(...);
myVector.add(x); // now x belongs to myVector and it should handle its lifetime
}
Edit1: I realize this is hard to understand this question without having an insight of what I'm trying to do. The class A is not complete but it essentially stand for a C-array "proxy" (or "viewer" or "sampler"). One interesting usage is too present a C-array as a 2d grid (the relevant function are not shown here). The property of this class are the following:
it should not own the data - no deep copyy
it should be copyable/assignable
it should be lightweight (
it should preserve constness (I'm having trouble with this one)
Please do not question the purpose or the design: they are the hypothesis of the question.
First some code:
class A
{
private:
float* m_pt;
public:
A(float* pt)
:m_pt(pt)
{}
const float* get() const
{
return m_pt;
}
void set(float pt)
{
*m_pt = pt;
}
};
void gfi()
{
float value = 1.0f;
const A ac(&value);
std::cout<<(*ac.get())<<std::endl;
A a = ac;
a.set(2.0f);
std::cout<<(*ac.get())<<std::endl;
}
Calling "gfi" generate the following output:
1
2
Assigning a with ac is a cheap way to shortcut the constness of ac.
Is there a better way to protect the value which m_pt point at?
Note that I DO want my class to be copyable/assignable, I just don't want it to loose its constness in the process.
Edit0: I also DO want to have a pointer in there, and no deep copy please (let say the pointer can be a gigantic array).
Edit2: thanks to the answers, I came to the conclusion that a "const constructor" would be a useful thing to have (at least in this context). I looked it up and of course I'm not the same one who reached this conclusion. Here's an interesting discussion:
http://www.rhinocerus.net/forum/language-c-moderated/569757-const-constructor.html
Edit3: Finally got something which I'm happy with. Thanks for your help. Further feedback is more than welcome
template<typename T>
class proxy
{
public:
typedef T elem_t;
typedef typename boost::remove_const<T>::type elem_unconst_t;
typedef typename boost::add_const<T>::type elem_const_t;
public:
elem_t* m_data;
public:
proxy(elem_t* data = 0)
:m_data(data)
{}
operator proxy<elem_const_t>()
{
return proxy<elem_const_t>(m_data);
}
}; // end of class proxy
void test()
{
int i = 3;
proxy<int> a(&i);
proxy<int> b(&i);
proxy<const int> ac(&i);
proxy<const int> bc(&i);
proxy<const int> cc = a;
a=b;
ac=bc;
ac=a;
//a=ac; // error C2679: binary '=' : no operator found which takes a right-hand operand of type...
//ac.m_data[0]=2; // error C3892: 'ac' : you cannot assign to a variable that is const
a.m_data[0]=2;
}
Your class is badly designed:
it should use float values, not pointers
if you want to use pointers, you probably need to allocate them dynamically
and then you need to give your class a copy constructor and assignment operator (and a destructor) , which will solve the problem
Alternatively, you should prevent copying and assignment by making the copy constructor and assignment op private and then not implementing them.
You can trick around with proxy pattern and additional run-time constness boolean member. But first, please tell us why.
Effectively your class is like an iterator that can only see one value. It does not encapsulate your data just points to it.
The problem you are facing has been solved for iterators you should read some documentation on creating your own iterator and const_iterator pairs to see how to do this.
Note: in general a const iterator is an iterator that cannot be incremented/decremented but can change the value it points to. Where as a const_iterator is a different class that can be incremented/decremented but the value it points to cannot be changed.
This is the same as the difference between const float * and float *const. In your case A is the same as float * and const A is the same as float *const.
To me your choices seem to be:
Encapsulate your data.
Create a separate const_A class like iterators do
Create your own copy constructor that does not allow copies of const A eg with a signature of A(A & a);
EDIT: considering this question some more, I think you are misinterpreting the effect of const-correctness on member pointers. Consider the following surprising example:
//--------------------------------------------------------------------------------
class CNotSoConstPointer
{
float *mp_value;
public:
CNotSoConstPointer(float *ip_value) : mp_value(ip_value) {}
void ModifyWithConst(float i_value) const
{
mp_value[0] = i_value;
}
float GetValue() const
{
return mp_value[0];
}
};
//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
float value = 12;
const CNotSoConstPointer b(&value);
std::cout << b.GetValue() << std::endl;
b.ModifyWithConst(15);
std::cout << b.GetValue() << std::endl;
while(!_kbhit()) {}
return 0;
}
This will output 12 and then 15, without ever being "clever" about the const-correctness of the const not-so-const object. The reason is that only the pointer ITSELF is const, not the memory it points to.
If the latter is what you want, you'll need a lot more wrapping to get the behavior you want, like in my original suggestion below or Iain suggestion.
ORIGINAL ANSWER:
You could create a template for your array-proxy, specialized on const-arrays for the const version. The specialized version would have a const *m_pt, return a const pointer, throw an error when you try to set, and so on.
Edit: Something like this:
template<typename T>
class TProxy
{
T m_value;
public:
TProxy(T i_t) : m_value(i_t) {};
template<typename T>
TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.m_value) {}
T get() { return m_value; }
void set(T i_t) { m_value = i_t; }
};
template<typename T>
class TProxy<const T *>
{
const T *mp_value;
public:
TProxy(const T *ip_t) : mp_value(ip_t) {};
template<typename T>
TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.mp_value) {}
T get() { return m_value; }
};
Why not replace float* with float in A. If you don't either the original owner of the float that the float* references can change it, or anyone prepared to do a mutable cast on the return value from a::get.
const is always just a hint to the compiler; there are no ways to make a variable permanently read-only.
I think you should use deep copy and define your own assingment operator and copy constructor.
Also to return handle to internal data structure in not a good practice.
You can deny the copy-constructor for certain combinations of arguments:
For instance, adding the constructor;
A(A& a) :m_pt(a.m_pt) { m_pt = a.m_pt; }
prevents any instance of A being initialised with a const A.
This also prevents const A a2 = a1 where a1 is const, but you should never need to do this anyway, since you can just use a1 directly - it's const even if you could make a copy, a2 would be forever identical to a1.