views:

163

answers:

4

I have a function like (please don't care about returning temporary by reference. This is just an example to explain the problem),

const foo<const int>& get_const()
{
    foo<int> f;
    return f;
}

This obviously won't compile. I am looking for a way to ensure callers won't change the T of foo. How can I ensure that?

I have seen the similar behavior for boost::shared_ptr. shared_ptr<T> is convertible to const shared_ptr<const T>. I couldn't figure out how it is doing this.

Any help would be great.

A: 

If I'm not mistaken, the boost::shared_ptr implementation has a non-explicit constructor that takes a const T& reference as an argument and then uses a const_cast on the RHS's pointer to remove the const, allowing implicit conversions between them.

Something like this:

shared_ptr(const shared_ptr<const T>& r) : ptr(const_cast<T*>(r.ptr)) {}

Is that what you're looking for?

Peter Alexander
`const_cast` == Red herring for me :) The OP is looking for something that does the opposite in fact (which is much more natural).
Matthieu M.
+1  A: 

Assuming that Foo is defined something like this:

template<typename T> class Foo
{
public:
    Foo(const T& value) : m_value(value) { }
    const T& getValue() const { return m_value; }
    void setValue(const T& value) { m_value = value; }
private:
    T m_value;
};

Then, in order to ensure that clients of Foo do not modify m_value (I assume that this is what is meant by "I am looking for a way to ensure callers won't change the T of foo"), you need to const-qualify the Foo object rather than its template parameter, i.e.

Foo<int> x(1);
x.setValue(2); // OK
const Foo<int> y(1);
y.setValue(2); // does not compile

Therefore, your get_foo function should return a const Foo<T>&, not a const Foo<const T>&.

Here's a complete, compilable example:

#include <iostream>

template<typename T> class Foo
{
public:
    Foo(const T& value) : m_value(value) { }
    const T& getValue() const { return m_value; }
    void setValue(const T& value) { m_value = value; }
private:
    T m_value;
};

template<class T> class Owner
{
public:
    Owner(const T& value) : m_foo(value) { }
    Foo<T>& getFoo() { return m_foo; }
    const Foo<T>& getConstFoo() const { return m_foo; }

private:
    Foo<T> m_foo;
};


int main(int argc, char** argv)
{
    Owner<int> x(1);
    x.getFoo().setValue(2);
    // x.getConstFoo().setValue(3); // will not compile
}
Gareth Stockwell
While this is a great answer, there are cases when `const foo<const T>` might really be what is needed, particularly if `T` is a pointer type or if foo stores a `T*`.
Tyler McHenry
@Tyler Agreed, the OP was somewhat unclear - while it does ask how to convert Foo<T> into Foo<const T>, it also states "I am looking for a way to ensure callers won't change the T of foo", which is what my answer aims to achieve by const-qualifying the Foo object itself.
Gareth Stockwell
Thanks for the answer. I am looking for what @Tyler mentioned.
Appu
+6  A: 

The compiler sees foo<T> and foo<const T> as two completely different and unrelated types, so the foo class needs to support this explicitly just as with any other conversion. If you have control over the foo class, you need to provide a copy constructor or an implicit conversion operator (or both).

template<typename T>
class foo 
{
 public: 

   // Regular constructor
   foo(T t) : t(t) {}

   // Copy constructor (works for any type S convertable to T, in particular S = non-const T if T is const)
   // Remember that foo<T> and foo<S> are unrelated, so the accessor method must be used here
   template<typename S> foo (const foo<S>& copy) : t(copy.getT()) {}

   // Accessor
   T getT() const { return t; }

   // Conversion operator
   operator foo<const T> () const { return foo<const T>(t); }

 private:

   T t;
};
Tyler McHenry
Great. Thanks very much.
Appu
A: 

First of all, you're returning a local object by reference...that's not good.

foo and foo are two different types so you'll have to write code (conversion constructors) to explicitly convert them.

To get what you wanted, consider this:

template <typename T>
struct foo {T* t;};

const foo<int>& get_const(const foo<int>& f) {
    return f;
}

foo<int> f;
const foo<int>& cf = get_const(f);
f.t = 0; // ok, f is not const
*cf.t = 0; // ok because cf.t is const but what cf.t points to is not
cf.t = 0; // compiler error cf.t is const and cannot be lvalue

foo<int>& cf = get_const(f); // compiler error, cannot convert non-const to const without const_cast

If you done your encapsulation correctly and only access members with const getter and non-const setters, this should be good enough for you. Remember if people really want to change your object, they can always const_cast. Const-correctness is only to catch unintentional mistakes.

Shing Yip