Why does a const reference to volatile int need a static_cast? - c++

Given the following code:
struct Foo {
volatile int i;
};
const int& bar = foo.i;
I get:
error: invalid initialization of reference of type 'const int&' from expression of type 'volatile int'
Unless I provide a static_cast<volatile int>.

"Volatility" of types in C++ works in pretty much exactly the same way as "const-ness" -- that is, an object that is volatile cannot be assigned to a non-volatile reference, in just the same way that
const int i = 3;
int& j = i; // won't work
Similarly, only methods marked volatile can be called on a volatile object:
struct S
{
void do_something() volatile {}
void do_something_else() {}
};
volatile S s;
s.do_something(); // fine
s.do_something_else(); // won't work
Methods can be overloaded by "volatility", in the same way they can be overloaded by const-ness.
In C++ standardese, these things are known as cv-qualifiers, to emphasise that they work in exactly the same way. (C99 adds a third that works in the same way, restrict, available as an extension in some C++ compilers). You can change cv qualifiers using const_cast<> -- the more powerful static_cast<> isn't required.
EDIT:
Just to make clear, a type can have both const and volatile modifiers at the same time, giving a total of four possibilities:
int i;
const int j;
volatile int k;
const volatile int l;

As with const, a volatile object can only be referred to by a volatile reference. Otherwise, the compiler has no way of knowing that accesses to the object via the reference need to be volatile.
volatile const int& bar = foo.i;
^^^^^^^^
Unless I provide a static_cast<volatile int>
It's rarely a good idea to silence a compiler error by adding casts. This doesn't give you a reference to foo.i, but to a separate copy of it.
If you are doing something really strange that requires a non-volatile reference to a volatile object, you'd need const_cast to remove the qualifer:
const int& weird = const_cast<int&>(foo.i);

Related

Are some usages of const only really useful when returning or passing things by reference? Or do they have subtle uses I'm not seeing

I've read about the various places to put const. Some usages seem clearly useful to me. Others however evade me. It would be really helpful if someone could confirm or correct my understanding as I explain my mental model of things.
These are the same. I'm not sure I understand why this would ever be useful, though. Does it perhaps allow one to initialize const int variables with a function, which in turn allows some compiler optimizations?
const int foo();
int const foo();
These are the same. The returned pointer cannot be used (via dereferencing) to change the values pointed to.
const int * foo();
int const * foo();
This means the returned pointer itself cannot be changed. But, why would it matter if the caller essentially decides to ignore the returned pointer and set it to something else? Is this only really useful if the pointer is returned by reference?
int * const foo();
These are the same. It means you can only pass in const ints, which allows the compiler to optimize things.
int foo(const int foo);
int foo(int const foo);
This means the passed-in pointer cannot be changed. I'm wondering here too, why would it matter unless the pointer is being passed in by reference?
int foo(int * const foo);
This (as a member function) guarantees that the function won't change the state of the object. Also, if the object itself is declared const, then it will only be able to call such functions.
int foo(int foo) const;
const int as return type is pointless, because in an expression the type of a non-class prvalue result of a function call will be stripped of its top-level const anyway. There is no distinction between a const and non-const prvalue of a non-class type. So the type of foo() is just int, no matter whether it is declared to return const int or int.
It would be different if the return type was a const qualified class type, e.g. const std::string foo(). In that case the const would disallow calling non-const-qualified member functions directly on the result of foo(), e.g. foo().resize(42). Still, this is a very rarely used distinction. And as noted in the comments, under certain conditions it can prevent move operations. E.g. in the above if we have a std::vector<std::string> v;, then v.push_back(foo()) will cause a copy, rather than a move, of the returned string into the vector.
However, the const qualifier is part of the return type in the function type and therefore it is technically possible to differentiate a function declared with const return type from one without it. The type of int foo(int foo) is int(int), but the type of const int foo(int foo) is const int(int). (However overloading based on return type is not possible for non-template functions anyway. The return type is not part of the function signature.)
correct
Same as 1. The type of foo() is simply int*.
The top-level const in the function parameter does not affect the type or signature of the function (in contrast to 1. where it does affect the type). So int foo(const int foo); and int foo(int foo); declare the same function and both have type int(int). Top-level const also doesn't affect how a variable can be initialized, so it doesn't make sense to say "you can only pass in const int". There are no const-qualified prvalues of type int anyway and if int foo can be initialized from some expression, then so can const int foo. The const has no implication on initialization or overload resolution. However const can be used like this in a function definition to tell the compiler and yourself that this parameter is not intended to be modified in the definition.
Same as 4.
This is the correct idea, although in the details it is not strictly true. Rather the const is only relevant to overload resolution (behaving as if the implicit object parameter was a const reference) and the type of this (which will be a pointer to const). It is still possible to mutate members declared as mutable or to use const_cast to mutate members. It is also not relevant whether the object itself is const, only whether the glvalue through which the member function is called is.

Shouldn't "const references" be actually called "references to const"?

I came up with this feeling when playing with constexpr references. But the issue itself is not related to constexpr, it is merely revealed by it.
We know that there are "pointers to const" and there are "const pointers". BTW, since the latter are much less used than the former, their name is often actually used to reference the former. Anyway, there is no this distinction for references, since they are not rebindable. Thus we only have "const references" and not "references to const". This is the common terminology and it is used in the standard as well. But it does not seem right to me.
Let's take a look at some well-known examples, commented with this common terminology:
int i = 0; // Value
int* pi = &i; // Pointer to i
const int* cpi = &i; // Pointer to const i
int const* cpi = &i; // Pointer to const i (east-const style)
int* const pci = &i; // Const pointer to i
int& ri = i; // Reference to i
const int& rci = i; // Const reference to i
int const& rci = i; // Const reference to i (east-const style)
Now let's take a closer look on several lines from the above:
const int* cpi = &i; // Pointer to const i
const int& rci = i; // Const reference to i
const int* declares "pointer to const", while const int& declares "const reference". Same order in the declaration, different order in the meaning. There is a clear inconsistency here. But if we call it "reference to const" inconsistency is gone. Same order on both sides.
The tension intensifies with the introduction of constexpr:
constexpr int& cri = i; // Constexpr reference to i
constexpr const int& crci = i; // Constexpr const reference to i
"Constexpr const" reference? Huh? The documentation clearly says:
A constexpr specifier used in an object declaration implies const.
A reference is an object. So why would we need const when it is already constepxr?.. Yet we need it, if i is const. Looks frustrating.
But the frustration is no more with help of "reference to const". Let's call crci "constexpr reference to const i". Now it is clear that const is applied to the referenced object, while constexpr is applied to the reference itself, stating the fact it can be used in constexpr context. And also that it is const, since all references are const. Makes perfect sense. To make it more consistent with the pointers, we might even have used the following hypothetical syntax:
const int& constexpr crci = i; // Constexpr reference to const i
const int* const pci = &i; // Const pointer to const i (perfect consistency)
But that's not how C++ works, for better or worse.
This concept of "reference to const" really helped me to understand "constexpr const reference" thing.
What do you think? Does the concept of "reference to const" sound convincing to you?
Yes, the proper name for const int & is a "reference to const int".
It's often called a "const reference to int" (probably because it sounds better to some), but technically that's a misnomer.
Even though they are not rebindable, references themselves are never const (i.e. std::is_const_v always equals to false for a reference).
constexpr const int& crci
"Constexpr const" reference? Huh? ... Let's call crci "constexpr reference to const i"
That's correct, crci is a "constexpr reference to const i".
To make it more consistent with the pointers, we might even have used the following hypothetical syntax:
const int& constexpr crci = i; // Constexpr reference to const i
const int* const pci = &i; // Const pointer to const i (perfect consistency)
To be honest, I don't see how this proposal improves consistency. int *constexpr pci; is not allowed for pointers, so why should it be allowed for references?
Things seem to be reasonably consistent to begin with:
constexpr const int *a; // constexpr (and const) pointer to const int
constexpr const int &a; // constexpr (but not const) reference to const int
Yes, adding constexpr doesn't make a reference itself const, but I don't think it needs to be fixed. (What would be the difference between a const refenrece and a non-const reference?)
Note that constexpr is not a part of a type, but it's a property of a variable. (e.g. constexpr int x; declares x with the type const int, and not some hypothetical constexpr int).
Since volatile tends to be also be relevant, the standard often uses "reference to a cv-(un)qualified" in normative text to cover both qualifiers, which conforms to your preference.
With quick search, besides the code example that you quoted, the standard appears to use (non-) const reference in following places, all in the standard library spec:
[container.node.observers]
key_type& key() const;
Returns: A non-const reference to ...
[rand.req.adapt]
... The expression a.base() shall be valid and shall return a const reference ...
[futures.shared.future
— (19.1) shared_future::get() returns a const reference to ...
Arguably, the wording could be improved by using reference to const.
Outside of the standard, "references to const" is used. But so is const reference, which is sometimes chosen possibly due to terseness.
In fact, pointers to const are also colloquially often called const pointers even though that meaning is ambiguous, which easily leads to confusion. In case of references, there is no ambiguity to those who know that references cannot be top level qualified. Beginners won't know that, so the use of "reference to const" is important in introductory material in my opinion. But so is perhaps explanation that const reference is used to mean the same thing.

cv-qualified struct's member is not similarly cv-qualified

According to this answer, the following code should be compiled without error:
#include <type_traits>
namespace
{
struct A { int i; };
volatile A a{};
static_assert(std::is_volatile< decltype(a) >{});
static_assert(std::is_volatile< decltype(a.i) >{});
}
but there is a hard error:
main.cpp:10:1: error: static_assert failed
static_assert(std::is_volatile< decltype(a.i) >{});
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Live example with clang 3.6.0.
Is it a clang bug or am I missing something substantial?
ADDITIONAL:
#include <type_traits>
namespace
{
struct A { int i; };
const A a{};
static_assert(std::is_const< decltype(a) >{});
static_assert(std::is_const< decltype(a.i) >{});
}
Exactly the same behaviour for the latter code snippet.
ADDITIONAL:
static_assert(std::is_volatile< std::remove_pointer_t< decltype(&a.i) > >{});
not cause an error.
This is the correct behaviour; the current decltype isn't testing what you think it tests. You have to change your decltype if you want to test for volatile-ness.
It is possible to copy a value out and drop volatile-ness. But a reference must be volatile reference-to-volatile.
int foo() {
int i = a.i; // volatile not needed
volatile int &i = a.i; // volatile needed
}
The decltype is treating a.i as an rvalue, not an lvalue, doing what decltype does by default and is doing a "naive" text analysis and reporting the type of A::i as written in the source code; therefore, decltype(a.i) is int.
Putting () around an expression in decltype changes its behaviour (in good ways usually, for this kind of question), giving us an lvalue when appropriate. Therefore, decltype(( a.i )) is volatile int &.
Now, you might expect that is_volatile< volatile int & >::value would be true. But it's not. We need to remove the reference before we can test for the volatile-ness.
static_assert(std::is_volatile< std::remove_reference_t<decltype((a.i))> >{});
Finally, you might be surprised that volatile int & isn't volatile according to is_volatile. But I guess it's because references are really very like pointers - and the 'pointer' inside a reference isn't volatile, even if the object pointed to is volatile. That's my theory anyway. const int& also does not satisfy is_const.

Temporary mutability of a member variable

Is it possible to have a member variable only be considered mutable for a given function/code block?
e.g.
class Foo() {
int blah;
void bar() const {
blah = 5; // compiler error
}
void mutable_bar() const {
blah = 5; // no compiler error
}
}
note: in this case I do NOT want to get rid of the const at mutable_bar since logical const will be preserved.
Same question, but different perspective: Can I somehow apply the mutable keyword to a method instead of a variable?
No, it is not possible, at least in C++. You need either mutable or non const function.
Also there is const_cast don't use it to modify things. In case if you modify const_casted const value you get Undefined Behaviour.
5.2.11 Const cast
7 [ Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer
to data member resulting from a const_cast that casts away a const-qualifier73 may produce undefined
behavior (7.1.6.1). —end note ]
7.1.6.1 The cv-qualifiers
4 Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.
....
5 For another example
struct X {
mutable int i;
int j;
};
struct Y {
X x;
Y();
};
const Y y;
y.x.i++; // well-formed: mutable member can be modified
y.x.j++; // ill-formed: const-qualified member modified
Y* p = const_cast<Y*>(&y); // cast away const-ness of y
p->x.i = 99; // well-formed: mutable member can be modified
p->x.j = 99; // undefined: modifies a const member
—end example ]
It is technically possible to bypass the const in this case, by for example:
void mutable_bar() const {
int& p_blah = const_cast<int&>(blah);
p_blah = 5; // no compiler error
}
Or some similar construct. But you are really jumping through hoops to do something that you shouldn't be able to do. And as a comment on another post says, this is "undefined behavior", which means that in some cases it may not even work (or do what you expect it to do).
You could use a const_cast to make the member not-const in selected cases. This, along with an according comment, might even be a relatively clean solution. At least it's explicit that you break the const in a restricted scope, instead of making it world-wide mutable.

constant references with typedef and templates in c++

I heard the temporary objects can only be assigned to constant references.
But this code gives error
#include <iostream.h>
template<class t>
t const& check(){
return t(); //return a temporary object
}
int main(int argc, char** argv){
const int &resCheck = check<int>(); /* fine */
typedef int& ref;
const ref error = check<int>(); / *error */
return 0;
}
The error that is get is invalid initialization of reference of type 'int&' from expression of type 'const int'
This:
typedef int& ref;
const ref error;
Doesn't do what you think it does. Consider instead:
typedef int* pointer;
typedef const pointer const_pointer;
The type of const_pointer is int* const, not const int *. That is, when you say const T you're saying "make a type where T is immutable"; so in the previous example, the pointer (not the pointee) is made immutable.
References cannot be made const or volatile. This:
int& const x;
is meaningless, so adding cv-qualifiers to references has no effect.
Therefore, error has the type int&. You cannot assign a const int& to it.
There are other problems in your code. For example, this is certainly wrong:
template<class t>
t const& check()
{
return t(); //return a temporary object
}
What you're doing here is returning a reference to a temporary object which ends its lifetime when the function returns. That is, you get undefined behavior if you use it because there is no object at the referand. This is no better than:
template<class t>
t const& check()
{
T x = T();
return x; // return a local...bang you're dead
}
A better test would be:
template<class T>
T check()
{
return T();
}
The return value of the function is a temporary, so you can still test that you can indeed bind temporaries to constant references.
Your code gives error because the const qualifier in const ref error is just ignored because 8.3.2/1 says
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3) or of a template type argument(14.3), in which case the cv-qualifiers are ignored.`
So error has type int& not const int& .
It's a very common mistake for English speaking people, because of the way the English grammar works.
I consider it extremely unfortunate that the C++ syntax would allow both:
const int // immutable int
int const // immutable int
to have the same meaning.
It doesn't make it easier, really, and isn't composable since:
const int* // mutable pointer to immutable int
int* const // immutable pointer to mutable int
certainly do NOT have the same meaning.
And this, unfortunately for you, what kicks in here, as #GMan explains.
If you wish to avoid this kind of error in the future, take the habit of qualifying your types (const and volatile) on their right, then you'll be able to treat a typedef as simple text replacement.
To maintain consistency with the Right Left Rule, I prefer to use 'cv' qualifiers like so.
int const x = 2; // x is a const int (by applying Right Left rule)
int const *p = &x; // p is a pinter to const int
In your example, I would write const ref error = check<int>(); like so
ref const error = check<int>(); // parsed as error is a const reference to an integer
As #Prasoon Saurav pointed out, cv qualifiers are ignored when introduced through typedef because as #GMan also says, that cv qualified references are ill-formed.
Therefore the declaration is effectively as below, which of course is an error.
int &error = check<int>();
Check out this for more information.
This is compiled:
typedef const int& ref;
ref error = check<int>();
VC++ compiler gives some explanation of your error: qualifier applied to reference type; ignored. Reference type must be declared as constant, const cannot be applied later.