views:

267

answers:

3

Please consider the following code.

struct foo
{
};

template<typename T>
class test
{
public:   

    test() {} 

    const T& value() const
    {
        return f;
    }

private:
    T f;
};


int main()
{
    const test<foo*> t;
    foo* f = t.value();
    return 0;
}

t is a const variable and value() is a constant member-function which returns const T&. AFAIK, a const type is not assignable to a non-const type. But how foo* f = t.value(); compiles well. How this is happening and how can I ensure value() can be only assigned to const foo*?

Edit

I found that, this is happening on when templates are used. Following code works as expected.

class test
{
public:   

    test() {} 

    const foo* value() const { return f; }

private:
    foo* f;
};


int main()
{
    const test t;
    foo* f = t.value(); // error here
    return 0;
}

Why the problem is happening when templates are used?

+6  A: 

Because you have two levels of indirection - in your main function, that call to value returns a reference to a const pointer to a non-const foo.

This can safely be copied into non-const pointer to a non-const foo.

If you'd instantiated test with const foo *, it would be a different story.

const test<const foo*> t;
foo* f = t.value(); // error
const foo* f = t.value(); // fine
return 0;

Update

From the comment:

value() returns const T& which can only be assigned to another const type. But in this case, compiler is safely allowing the conversion.

Const data can only be read. It cannot be written ("mutated"). But copying some data is a way of reading it, so it's okay. For example:

const int c = 5;
int n = c;

Here, I had some const data in c, and I copied the data into a non-const variable n. That's fine, it's just reading the data. The value in c has not been modified.

Now, suppose your foo had some data in it:

struct foo { int n; };

If I have a non-const pointer to one of those, I can modify the n value through the pointer. You asked your test template to store a pointer to a non-const foo, and then made a const instance of test. Only the pointer address is constant, therefore. No one can change the address stored in the pointer inside test, so it cannot be made to point to another object. However, the object it points to can have its contents modified.

Update 2:

When you made your non-template version of the example, you made a mistake. To get it right, you need to substitute foo * into each place where there's a T.

const T& value() const

Notice that you have a reference to a const T there. So the return value will be a reference to something const: a foo *. It's only the pointer address that can't be modified. The object it points to can have its contents modified.

In your second example, you got rid of the reference part, which changes the meaning and makes the const modifier apply to the object that the pointer points to, instead of applying to the pointer itself.

Daniel Earwicker
Appu
@Appu This is the different between pointer to const and const pointer to non-const. No idea why you can't "make" `test<const foo*>`.
Amit Kumar
Please see the edit. I have a non-const member `f` and returning `const` from it. In that case compile is complaining correctly. Looks like the behavior is different when templates are used.
Appu
Nick Meyer
Ahh, I got it now. Thank you for the detailed explanation. I really appreciate it. So do you think partial specialization for pointer type is the way to solve this?
Appu
It totally depends on the situation. If it's a big complex template, a specialization might involve much duplication - it's better to design a system of templates with specialization in mind from the start. Maybe if you gave more details about the wider problem - or maybe that should be in other questions.
Daniel Earwicker
@Earwicker, since you're being extra careful about language here, I'd suggest you say, "the return value will be a reference to const pointer to non-const `foo`". I know you're using the phrase "const reference" casually to mean "reference to const", but since where the consts go is the crux of this confusion, it might help to be extra-pedantic.
Jesse Beder
@Jesse - true, it's a lazy habit. As long as the reader knows that all references are const in that other sense, it can't be misinterpreted: it can only be shorthand for reference-to-const, but if they don't then I guess it could easily cause further confusion in discussions like this.
Daniel Earwicker
+1  A: 

Use the following template specialization:

template<typename T>
class test<T*>
{
public:

    test() {}

    const T* value() const
    {
        return f;
    }

private:
    T* f;
};

After including this, g++ says:

d.cpp: In function ‘int main()’:
d.cpp:41: error: invalid conversion from ‘const foo*’ to ‘foo*’
Amit Kumar
+1  A: 

There's nothing wrong in your code, having a const reference to a pointer only means that you can't modify the pointer, but the pointed-to object remains perfectly mutable. If inside your main function you try to change the address pointed to by the f member of t you'll see that you can't: encapsulation is perfectly preserved.

This is the same principle that makes the following code valid:

void foo(std::vector<int *> const & v)
{
    *v[0] = 0; // op. [] returns const & to int *
}

People new to C++ are usually surprised by this behavior, because for them a const vector should not allow the modification of its elements. And in fact it doesn't, because the pointer stored in the vector does not change (it keeps pointing to the same address). It's the pointed-to object which is modified, but the vector does not care about that.

The only solution is to do as Amit says and provide a specialization of your class for T*.

Manuel