Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

Is there any accepted way in C++ to differentiate between const references to immutable objects vs. mutable ones?

e.g.

class DataBuffer {
   // ...
};

class Params {
   // ...
};

class C {
public:
    // Given references must be valid during instance lifetime.
    C(const Params& immutableParameters, const DataBuffer& mutableDataBuffer) :
        m_immutableParameters{immutableParameters},
        m_mutableDataBuffer{mutableDataBuffer}
    {
    }

    void processBuffer();
private:
    const Params& m_immutableParameters;
    const DataBuffer& m_mutableDataBuffer;
};

Here the semantic difference is given just in the names.

The problem is that const& instance variables only let you know the object won't be modified by the instance. There is no distinction in the interface whether or not they may be modified elsewhere, which I think is a useful feature to be able to describe in the interface.

Expressing this through the type-system would help make interfaces clearer, allow the compiler to catch errors (e.g. accidentally modifying parameters handed to a C instance, outside of the instance, in the example above), and possibly help with compiler optimizations.

Assuming that the answer is that the distinction isn't possible in C++, maybe there is something close which can be achieved with some templates magic?

share|improve this question
1  
If you don't want programmers to be able to change a variable, then mark it const. I don't really understand your question though. – Rakete1111 16 hours ago
    
@Rakete1111 Assume C is given, and you want to describe its interface. In some cases, you may want to force users of the class to pass in a const reference to an immutable object, since that is, semantically, the interface which the class expects. – Danra 16 hours ago
    
When class C receives a const T& parameter, it can only assume he has a ready only access to an object for the lifetime of the function being executed (in this case the constructor). Therefore I think it is flawed to copy those references in members of C. maybe the example is just incorrect. – UmNyobe 16 hours ago
2  
@UmNyobe I see what you meant now. Yes, I do make the assumption that the references will be valid during the C instance's lifetime. – Danra 16 hours ago
1  
@Danra: "help make interfaces clearer" Since the language makes no distinction between the two cases, it makes your interface less clear, as you have to use hackery to prevent accidental calling of the function with a mutable object. Also, it's so easily worked around (as_const) that users will not understand what you were trying to accomplish. "possibly help with compiler optimizations" The compiler does not recognize a difference between an immutable reference and a "currently-const" mutable one. So no optimization is possible. – Nicol Bolas 15 hours ago
up vote 11 down vote accepted

Immutability is not part of the C++ type system. As such, you cannot differentiate between immutable objects and mutable ones. And even if you could, std::as_const will always ruin your attempt to do so.

If you are writing an interface that requires immutability of objects, the easiest way to handle this is to invoke the Fundamental Theorem of Software Engineering: "We can solve any problem by introducing an extra level of indirection." So make immutability part of the type system. For example (FYI: uses some small C++17 library stuff):

template<typename T>
class immutable
{
public:
  template<typename ...Args>
  immutable(std::in_place_t, Args &&...args) t(std::forward<Args>(args)...) {}

  immutable() = default;
  ~immutable() = default;

  immutable(const immutable &) = default;
  //Not moveable.
  immutable(immutable &&) = delete;

  //Not assignable.
  immutable operator=(const immutable &) = delete;
  immutable operator=(immutable &&) = delete;

  const T* operator->() const {return &t;}
  const T& operator*() const {return t;}

private:
  const T t;
};

With this type, the internal T will be immutable regardless of how the user declares their immutable<T>. Your C class should now take an immutable<Params> by const&. And since immutable<T> cannot be constructed from a copy or move of an existing T, the user is forced to use immutable<Params> whenever they want to pass that as a parameter.

Of course, your biggest danger is that they'll pass a temporary. But that was a problem you already needed to solve.

share|improve this answer
    
Nice! I wonder if this will allow the compiler make optimizations which aren't possible with a regular const& parameter. My guess is it would, since just from looking at the parameter it can tell the inner t cannot be changed by outside code. – Danra 11 hours ago
    
@Danra: probably there are no optimisations that are made on the back of this. The compiler already assumes that data isn't going to be changed by other code unless it is explicitly told that it can be (see for instance all the problems of using volatile in multi-threaded code, which means values can be changed by another thread, and the compiler still optimises to incorrect code!) – dave 6 hours ago
    
@dave AFAIK that's plain wrong. If you store a plain const reference, then most of the times when you access it (say each time you first access it within a new method call) the compiler must assume some other unrelated code which has a non const reference to the object might have changed the object, therefore the object must be dereferenced. – Danra 2 hours ago
1  
@Danra: And how far would that extend? After all, you have to use * or -> to get at the object, which ultimately leads to a reference (or this pointer). Can every const member function of T assume that it will be invoked on an immutable object? No. After lots of inlining, the compiler might be able to trace the pointer or reference back to the immutable. But unless it inlined the functions you use that T with, then it won't know that the T is immutable. – Nicol Bolas 2 hours ago

I don't know the reason, but here's how you can do it:

struct C {
   template<typename T, typename T2>
   C(T&&, const T2&&) = delete;

   C(const Params&, const DataBuffer&) { /*...*/ }
};

By declaring a constructor that takes any argument by non-const reference, it will always be a better match than the constructor taking const&, as a cv-qualifier doesn't have to be added.

The const& constructor is a better match when passing a const parameters, as the cv-qualifier doesn't have to be removed.

DataBuffer db;

const Params cp;
C c{ cp, db }; // ok, second constructor call is chosen

Params p;
C c2{ p, db }; // error, constructor is deleted

Due note that, as @IgorTandetnik said, you can break your requirement easily:

Params pa;
const Params& ref_pa = pa;
C c3{ ref_pa, db }; // ok, but shouldn't compile.
share|improve this answer
2  
Could probably still be fooled with something like DataBuffer db; const DataBuffer& dbRef = db; C c(..., dbRef);. Perhaps not likely to happen deliberately, but a function that itself takes a const ref can pass it along to C. – Igor Tandetnik 16 hours ago
1  
@IgorTandetnik True, but there is no way to prevent such use. Although, if someone does this, they probably want to break the rules :) – Rakete1111 16 hours ago
1  
C++17's std::as_const function will make it much easier to break such code. – Nicol Bolas 15 hours ago
    
@Rakete1111 I thought I explained the reason already :) - to describe C's interface better. Upvoted, interesting. Could you please edit the answer to match the question? Just the first parameter is supposed to be forced immutable. – Danra 15 hours ago
1  
@Danra I still don't understand why you would want to do this :) OK, sorry. Forgot than the second one doesn't have to be const :) – Rakete1111 15 hours ago

As previous answers, C++ doesn't have the concept of "immutable". @Rakete1111 gave you the answer I would have used. However, Visual Studio will put global const variable in .rdata segment, where other variables will go to .data. The .rdata segment will generate a fault when trying to write.

If you need a run time test whether an object is read only, use a signal handler, like this:

#include <csignal>
const int l_ci = 42;
int l_i = 43;

class AV {};
void segv_handler(int signal) {
    throw AV{};
}

template <typename T>
bool is_mutable(const T& t)
{
    T* pt = const_cast<int*>(&t);

    try {
        *pt = T();
    }
    catch (AV av) {
        return false;
    }

    return true;
}

void test_const()
{
    auto prev_handler = std::signal(SIGSEGV, segv_handler);
    is_mutable(l_i);
    is_mutable(l_ci);
}
share|improve this answer

What you need is not a const reference, but a const object. Value semantics solve your problem. Nobody can modify a const object. While a reference is only const where it is marked const, because the referenced object may not be const. Take that for example :

int a;

int const& b = a;

// b = 4; <-- not compiling, the reference is const

// (1)

At (1), a is int, and b is a reference to const int. While a is not const, the language permit the reference to const to be bound on a non const object. So it's a reference to const object that is bound to a mutable object. The type system won't allow you to modify the mutable object through the reference, because it may have been bound to a const object. In our case it isn't, but the tribe don't change. However, even declaration of a reference to const won't change the original declaration. The int a is still a mutable object. I can replace the comment (1) by this:

a = 7;

This is valid, whenever reference or other variables have been declared. A mutable int can change, and nothing can prevent it from changing. Heck, even another program like cheat engine can change the value of a mutable variable. Even if you had rules in the language to guarantee that it won't be modified, there is nothing they will prevent the mutable variable from changing values. In any language. In machine language, a mutable value is permitted to change. However, maybe some API of the operating system can help you change the mutability of memory regions.


What can you do to solve this problem now?

If you want to be 100% sure an object won't be modified, you must have immutable data. You usually declare immutable objects with the const keyword :

const int a = 8;

int const& b = a;

// a cannot change, and b is guaranteed to be equal to 8 at this point.

If you don't want a to be immutable and still guarantee b to not change, use values instead of references :

int a = 8;

const int b = a;

a = 9;

// The value of b is still 8, and is guaranteed to not change.

Here, value sematic can help you have what you want.

Then const reference are there for what? There are there to express what you are going to do with the reference, and help enforce what can change where.


As the question has been further clarified, no there is no way to determine if the reference has been bound to a mutable or immutable object in the first place. There is, however, some tricks you can have to differentiate the mutability.

You see, if you want more information about the mutability to be passed along with the instance, you can store that information in the type.

template<typename T, bool mut>
struct maybe_immutable : T {
    using T::T;
    static constexpr auto mutable = mut;
};

// v--- you must sync them --v
const maybe_immutable<int, false> obj;

This is the most simple way to implement it, but a naive one too. The contained data will be conditionally immutable, but it forces you to sync template parameter and constness. However, the solution allows you to do this :

template<typename T>
void do_something(const T& object) {
    if(object.mutable) {
        // initially mutable
    } else {
        // initially const
    }
}
share|improve this answer
    
That's all clear. The question was whether there is a way to differentiate between const references to const (immutable) objects and const references to mutable objects. – Danra 11 hours ago
    
@Danra there is a solution that rudimentary do what you want. See my edits. – Guillaume Racicot 5 hours ago

I hope I understand you question correct it is not as explicit as so to say "D language" but with const r-value references you can make immutable parameters.

What I understand from immutable is forexample

void foo ( const int&&  immutableVar );
foo(4);-> is ok 
int a = 5;
foo(a);->is not ok 
share|improve this answer
2  
But then again, const int b = 5; foo(b) is also illegal. – Rakete1111 16 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.