4

Let's look at the following C++ code:

#include <iostream>

int main()
{
    int z = 2;

    class A {
        public:
        const int & x;
        A(const int & x) : x(x) {}
        void show(){
            std::cout << "x=" << this->x << std::endl ;
        }
    } a(z);

    a.show();
    z = 3;
    a.show();
}

The program prints: 2 and 3

It clearly shows that while inside class A x can't be modified, it merely means it's read only, because I can change it's value from outside.

Of course I can make it a copy stored inside class A, but I'm wondering if there is (or if there is a proposal?) of a way to say to class A that the member x will be truly constant instead of merely read only, with the meaning of a promise that the external code won't change it ?

To my eyes it looks like something related to the meaning of the C restrict keyword, but I've not heard of any such C++ feature yet. Do you ?

kriss
  • 23,497
  • 17
  • 97
  • 116
  • 4
    `const` on a reference just means 'this reference cannot modify the referred object'. it doesn't make much sense to me that a reference would be given the power to retroactively `const`ify someone else's variable. – underscore_d Dec 29 '15 at 11:03
  • you want to pass z to A telling A that this value will not ever be changed? what is the purpose of this thing? – Humam Helfawi Dec 29 '15 at 11:06
  • 3
    The only guarantee will be if you declare z itself `const`. In this case you won't be able to assign `3` to it of course. – Sergei Kulik Dec 29 '15 at 11:06
  • 2
    You can write this in library: Create a type `immutable`, and give `A` a constructor only from such an immutable. – Kerrek SB Dec 29 '15 at 11:12
  • 2
    For integers, thought, it'd be easier to just give `A` a data member of type `const int`. – Kerrek SB Dec 29 '15 at 11:13
  • The exemple is oversimplified. As I wrote I wonder if there is a way to make a *promise* to class A that what is provided as a constant is also a constant for external world, henceforth that the compiler is allowed to make as much assumptions it likes based on that to optimize the code (for instance pruning away alternative branches across inlined member methods calls or such). Of course in my exemple copying z to x will always be better, but it can be a concern for large external objects. – kriss Dec 29 '15 at 11:18
  • 1
    I don't see any "reference to constants" in your code. `const` != constant. – Emil Laine Dec 29 '15 at 11:18
  • @Kerrek SB: looks like what I'm looking for. Mind to make that an answer ? – kriss Dec 29 '15 at 11:25

4 Answers4

4

Constness is an attribute of the actual variable.

The term const int& x simply means "x is a reference to an int which it will not modify" and of course the compiler enforces this.

If you want the actual variable to which x refers to be const, simply declare it so:

#include <iostream>

int main()
{
    const int z = 2;    // declared const. Nothing may ever modify it

    class A {
    public:
        const int & x;
        A(const int & x) : x(x) {}
        void show(){
            std::cout << "x=" << this->x << std::endl ;
        }
    } a(z);

    a.show();
    z = 3;        // this is a logic error, caught by the compiler.
    a.show();
}

compiling correctly produces the error:

./const.cpp:41:7: error: read-only variable is not assignable
    z = 3;
    ~ ^
1 error generated.
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I like this answer - because if you're saying "I promise you, class A, that it will never change", then it needs to be `const` externally! – Moo-Juice Dec 29 '15 at 11:49
  • 1
    @Moo-Juice passing a `const &` to `class A` isn't promising the referenced variable will never change; it's just enforcing that `A` can't modify said variable _via that reference_. It's been alleged that D has a way to promise that the underlying variable is `const`, but I've not seen a relevant example of this in action yet. – underscore_d Dec 29 '15 at 11:52
  • 2
    @underscore_d I've had a look at the D reference. `immutable` is always part of a variable declaration, whereas `const` makes references const. In c++ both concepts are fully supported by the single keyword `const`. – Richard Hodges Dec 29 '15 at 12:05
  • 1
    @RichardHodges C++'s `const` is closer to D's `const` than to D's `immutbale`. In both cases `const` should actually be named `readonly`. In D the keyword `immutable` means that the variable will really never ever change its value, enabling some optimizations. This is not the case with C++. Also both `const` and `immutable` can be part of a variable declaration. And both can be used to modify reference types. Therefore, I believe your comment it simply incorrect. – Ralph Tandetzky Dec 29 '15 at 12:35
  • @RalphTandetzky I see your point. But there is nothing to prevent a c++ compiler from placing a const integral type into ROM (or even eliding its existence altogether). Assigning to an (actual) const variable indirectly (through const cast for example) would make a program ill-formed would it not? Perhaps `constexpr` is closer (and indeed more powerful than D's immutable) but this is the start of a long conversation... – Richard Hodges Dec 29 '15 at 12:42
  • @RicharHodges I agree, when declaring a variable const, or writing `const char * ptr = "Hello world!"` the compiler may choose to put the stuff into read-only memory and modifying it through `const_cast` is indeed undefined behaviour. But modifying a `const &` to something that is not initially const is not undefined behaviour. Otherwise, the use of `mutable` mutexes in `const` member functions would also be undefined behaviour. – Ralph Tandetzky Dec 29 '15 at 13:11
  • @RalphTandetzky agreed. an object containing a mutex will not be an integral type (or indeed a POD). – Richard Hodges Dec 29 '15 at 13:31
3

You're looking for D's immutable keyword, which was introduced as a new concept in that language precisely because, unfortunately, the answer is no: it does not exist in C++.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 2
    How is this an answer? 'You're looking for a different language'? edit: It's better after your edit, and the D fact is interesting, but... either way, I haven't been either of the downvoters. – underscore_d Dec 29 '15 at 11:04
  • 3
    @underscore_d: The answer is "no, it's not possible/does not exist". What else am I supposed to say when the answer is no? – user541686 Dec 29 '15 at 11:04
  • 2
    While we're on the D tangent, can you link to an example of how `immutable` achieves precisely what the OP requested? The synopses I've found don't seem relevant. – underscore_d Dec 29 '15 at 11:08
  • @underscore_d: it's interresting to me to know that C++ doesn't support something. And even more interresting answer would have been: *there is this obscure proposal yet below radars...* (too bad if there isn't). – kriss Dec 29 '15 at 11:22
  • 1
    Allowing later users of `const` references/pointers to retroactively make my variable `const` without my say-so would definitely be an "obscure proposal", yes. Or have I misunderstood what you want? I probably have! – underscore_d Dec 29 '15 at 11:25
  • 2
    The `const` keyword in c++ serves the purpose of both `immutable` and `const` in D. I'm afraid this answer is incorrect. reference: http://dlang.org/spec/const3.html – Richard Hodges Dec 29 '15 at 12:03
  • @RichardHodges I believe that your comment is incorrect as I just explained in the comments to your answer to the question. – Ralph Tandetzky Dec 29 '15 at 12:41
  • @underscore_d: the whole point is about separate compilation. What I see is that a const variable inside a class can't be fully used to optimized away parts of the code without the compiler knowing what is done on call sites. I want to tell the compiler that assuming that variable won't change is OK (if it changes it's programmers responsibility). It's the same kind of assumption introduced by the C restrict keyword. It allows things like calling some external function (outside of the object) knowing it won't introduce side effects on my constant members (or objects pointed to with restrict). – kriss Dec 29 '15 at 13:53
2

Constness in C++ does not mean immutability, but that the variable in question is read-only. It can still be modified by other parts of the program. I understand your question as to whether it's possible to enforce true immutability in a called function without knowing what the caller is doing.

Of course you can create a template wrapper class which accomplishes the task:

template <typename T>
class Immutable
{
public:
    template <typename ...Args>
    Immutable( Args&&...args ) 
        : x( std::forward<Args>(args)... )
    {}

    operator const T &() const
    {
        return x;
    }

private:
    const T x;
};

As long as you do not reinterpret_cast or const_cast you will have truly immutable objects when you wrap them with Immutable<T>.

However, if you have a constant reference to some object, there is no way to tell, if some other part of the program has a non-constant access to the object. In fact, the underlying object might be a global or static variable, that you have read-only access to, but functions you call might still modify it.

This cannot happen with Immutable<T> object. However, using Immutable<T> might impose an extra copy operation on you. You need to judge yourself if you can live with that and if the cost justifies the gain.

Having a function require an const Immutable<Something> & instead of const Something & as an argument affects the calling code. A copy operation might be triggered. Alternatively, you can ask for an Immutable<Something> & without the const. Then no accidental copies will be triggered, but the calling code must pass a reference to Immutable<Something> object. And rightly so, because if the caller received a const & as an argument then the caller does not know, whether the object might get modified by someone else in the program. The caller has to create the object itself or require an immutable object to be passed to it as a reference.

Your original question

Here's your original problem with Immutable<int> & instead of const int &.

#include <iostream>

int main()
{
    Immutable<int> z = 2;

    class A {
        public:
        const Immutable<int> & x;
        A(Immutable<int> & x) : x(x) {}
        void show(){
            std::cout << "x=" << this->x << std::endl ;
        }
    } a(z);

    a.show();
    //z = 3; // this would fail
    a.show();
}

An other example

Here's how it works: If you write

void printAndIncrementAndPrint( int & i1, const int & i2 )
{
    std::cout << i2 << std::endl;
    ++i1;
    std::cout << i2 << std::endl;
}

int main()
{
    int i = 0;
    printAndIncrementAndPrint( i, i );
}

then it will print

0
1

into the console. If you replace the second argument of printAndIncrementAndPrint() with const Immutable<int> & i2 and keep the rest the same, then a copy will be triggered and it will print

0
0

to the console. You cannot pass and Immutable<int> to the function and a int & to the same underlying data without breaking the typesystem using const_cast or reinterpret_cast.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • 2
    I don't see how this is different from making `z` or any of the `x`s `const int`. Can you add a usage example? – Emil Laine Dec 29 '15 at 11:50
  • 1
    Be very careful with this approach. High optimisation levels assume no aliasing, and this will give you incorrect behaviour in production. – Richard Hodges Dec 29 '15 at 12:09
  • 1
    You could add an example of how this solves the OP's original problem by adding a constructor `A::A(Immutable& i) : x(i) {}`. I'm deliberately making this a non-const reference, since we want to discourage temporaries. An alternative parameter type would be `const Immutable*`. – Kerrek SB Dec 29 '15 at 12:19
  • @RichardHodges Why would this give incorrect behaviour? Even high optimization levels must assume the worst case (i. e. aliasing) to be correct compiler implementations. – Ralph Tandetzky Dec 29 '15 at 12:30
  • 1
    `-O2` enables `-fstrict-aliasing` which allows the compiler to assume that dissimilar types are not at the same address. – Richard Hodges Dec 29 '15 at 12:35
  • @RichardHodges I'm afraid your comment is incorrect. The option `-fstrict-aliasing` allows the compiler to assume the strictest aliasing rules applicable to C++. This enables optimizations with `union`s, `reinterpret_cast`s and other non-portable constructs, but it will not lead to undefined behaviour where your in the legal language bounds. Also look at this question. http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – Ralph Tandetzky Dec 29 '15 at 12:48
0

I think this is a design problem for the programmers, not the language. A const variable means for any user of that variable, they should not change the value of that variable. Our compiler is smart enough to help us make sure of that. So A is a user of z and if you want A know that A::x references to a const variable, then you should make z a const int. The const reference is just to keep the contract between the user and the provider.

Jaege
  • 1,833
  • 4
  • 18
  • 32