Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access to reference in member variable discards constness

I made a wrapper around an object in my code that should modify accesses to the object. I choose to use an object here for testing instead of a functor that would have the same functionality. Basically: The wrapper receives a reference to the object and forwards all indexed accesses to the object (after some possible manipulation)
Now comes the problem: The accessor discards constness of the wrapped object.
Minimal Example

struct Foo
{
    std::array<int, 2> data;
    const int& operator()(int idx) const{
        return data[idx];
    }
    int& operator()(int idx){
        return data[idx];
    }
};

struct Bar
{
    Foo& ref;
    Bar(Foo& r):ref(r){}
    int& operator()(int idx) const{
        return ref(idx);
    }
};

template< typename T >
void test(const T& data){
    data(1) = 4;
    std::cout << data(1);
}

void main(){
    Foo f;
    test(f);
    // Above call does not compile (as expected)
    // (assignment of read-only location)
    Bar b(f);
    test(b); // This does compile and works (data is modified)
}

Declaring the ()-operator of Bar (the wrapper) "const", I'd expect to be all member accesses "const" to. So it shouldn't be possible to return an "int&" but only a "const int&"

However gcc4.7 happily compiles the code and the const is ignored. Is this the correct behavior? Where is this specified?

Edit: On a related issue: If use typedefs in Foo like:

struct Foo
{
    using Ref = int&;
    using ConstRef = const int&; //1
    using ConstRef = const Ref;  //2
    int* data; // Use int* to have same issue as with refs
    ConstRef operator()(int idx) const{
        return data[idx]; // This is possible due to the same "bug" as with the ref in Bar
    }
    Ref operator()(int idx){
        return data[idx];
    }
};

I noticed that //1 does work as expected but //2 does not. Return value is still modifiable. Shouldn't they be the same?

like image 549
Flamefire Avatar asked Feb 09 '23 19:02

Flamefire


1 Answers

Yes, this is correct behaviour. The type of ref is Foo &. Adding const to a reference type1 does nothing—a reference is already immutable, anyway. It's like having a member int *p. In a const member function, its type is treated as int * const p, not as int const * p.

What you need to do is add const manually inside the const overload if you want it there:

struct Bar
{
    Foo& ref;
    Bar(Foo& r):ref(r){}
    int& operator()(int idx) const{
        return const_cast<const Foo&>(ref)(idx);
    }
};

To address the question edit: no, the typedefs are not the same. const int & is a reference to a (constant int). const Ref is a constant Ref, that is, a constant (reference to int); parentheses used in mathematical sense.


1 I am talking about the reference type itself. Not to be confused with adding const to the type to which the reference refers.

like image 131
Angew is no longer proud of SO Avatar answered Feb 13 '23 03:02

Angew is no longer proud of SO