I see some code like:
Foo const& foo = val; // (A)
const Foo& foo = val; // (B)
Just different placement of const.
Though both will work fine, I would just want to know the proper grammar.
I read it as follows from R->L.
(A) foo is a const reference to type Foo - not sure if val is const
(B) foo is a reference to const type Foo - meaning val is const
Given that references are not objects, and technically all references are const (they can't refer to another object),
Grammar perspective, is (A) Foo const& foo = val; considered a "confusing" definition?
And just use the following instead:
const Foo& foo = val; // referring to const Foo
Foo& foo = val; // referring to non-const Foo
const applies to the thing on its left, unless nothing is there, then it applies to the thing on its right instead.
These two statements are identical:
Foo const& foo = val; // (A)
const Foo& foo = val; // (B)
They both mean the same thing:
foo is a non-const (1) reference to a const Foo object, and is being initialized as a reference to an object named val.
(1): by definition, a reference can't be changed once initialized, so const vs non-const doesn't really apply to a reference itself, as it is implicitly const. But const does apply to the thing that is being referenced.
Foo const& foo = val; // (A)
const Foo& foo = val; // (B)
Both are identical definitions and are just a matter of style:
reference to const object.
it would be different for:
const Foo* fooC = nullptr; // (C)
Foo* const fooD = nullptr; // (D)
You cannot reassign fooD but you can reassign fooC.
Object pointed by fooC cannot be modified through fooC.
Object pointed by fooD can be modified through fooD.
They're identical grammatically. And
I read it as follows from R->L.
(A) foo is a const reference to type Foo
I think you didn't read it correctly. It should be something like
Foo const &
Foo/const/reference to
then you should get "reference to const Foo`. So when reading from R->L, (A) is more clear.
Related
While looking into some code, I came across a construct with the following line:
if (const auto& foo = std::get_if<MyType>(&bar)) // note the ampersand!
where bar is a std::variant<MyType, OtherType>. The problem here is that get_if may return a null pointer and I don't understand why the statement works.
Consider this similar MCVE:
#include <iostream>
struct Foo { int a = 42; };
Foo* f() { return nullptr; }
int main() {
const auto& foo = f(); // Returns a nullptr that binds to Foo*& - UB?
//static_assert(std::is_same<decltype(foo), const Foo*&>::value); // -> Fails
//const Foo*& bar = f(); // -> Fails
if (foo) std::cout << foo->a << std::endl;
else std::cout << "nullpointer" << std::endl;
}
The first line of main() works fine, and I would expect the type of barto be const Foo*&, but the static assertion fails. Unsurprisingly, the following line also fails to compile with cannot bind non-const lvalue reference of type 'const Foo*&' to an rvalue of type 'const Foo*'.
What happens in the first statement of main? Is this UB or does the standard contain some hidden secret that allows this to be legal? What is the type of bar?
Note that for const auto& foo, const is qualified on the auto part, i.e. the pointer but not the pointee. Then the type of foo would be Foo* const &, which is a reference to const (pointer to non-const Foo), but not const Foo* &, which is a reference to non-const (pointer to const Foo).
And the lvalue-reference to const could bind to rvalue returned by f(), so const auto& foo = f(); works fine; const Foo*& bar = f(); won't work because bar is an lvalue-reference to non-const; which can't bind to rvalue. Changing the type of bar to const Foo * const & or Foo* const & (same as foo) would make it work.
Scott Meyers in this talk at 44:15, says const Rvalue references are used in c++0x standard library to capture certain overloads which are not supposed to be compilable.
Code snippet to illustrate the above mentioned point would be helpful. Thanks.
One usage that I found useful is to disable temporaries biding to reference members. For example, consider the code below:
struct Foo{};
class X
{
const Foo& _foo;
public:
X(const Foo&&) = delete; // prevents rvalue binding
X(const Foo& foo): _foo(foo){} // lvalue is OK
};
Foo get_Foo()
{
return {};
}
const Foo get_const_Foo()
{
return {};
}
Foo& get_lvalue_Foo()
{
static Foo foo;
return foo;
}
int main()
{
// X x1{get_Foo()}; // does not compile, use of deleted function
// X x2{get_const_Foo()}; // does not compile, use of deleted function
X x3{get_lvalue_Foo()}; // OK
}
You definitely want to disable a rvalue being passed to the constructor of X, since rvalues do not bind to const references via constructor parameters, so you end up with a dangling reference. Why const Foo&& and not simply Foo&&? Because if you use X(Foo&&) = delete;, then if your get_Foo() returns const Foo (which is a bad idea, but nevertheless is seen in actual code), it will bind to X(const Foo&) instead, and you end up with a dangling reference. However, X(const Foo&&) in the code above is a better match for a const Foo rvalue, so we obtain the desired effect of not being able to construct an X with a rvalue.
You may also ask why not defining X(Foo&) instead for the lvalue constructor. Then you won't be able to bind const lvalues. So the best approach is to mark X(const Foo&&) = delete;. Hope this clarifies it.
I am working on a matrix view class, of which constructor takes a matrix as a parameter and binds it to a const reference member. I would very much like to avoid binding rvalues, since they don't bind via a constructor parameter, and we end up with a dangling reference. I came up with the following (simplified code):
struct Foo{};
class X
{
const Foo& _foo;
public:
X(const Foo&&) = delete; // prevents rvalue binding
X(const Foo& foo): _foo(foo){} // lvalue is OK
};
Foo get_Foo()
{
return {};
}
const Foo get_const_Foo()
{
return {};
}
Foo& get_lvalue_Foo()
{
static Foo foo;
return foo;
}
int main()
{
// X x1{get_Foo()}; // does not compile, use of deleted function
// X x2{get_const_Foo()}; // does not compile, use of deleted function
X x3{get_lvalue_Foo()}; // this should be OK
}
Basically I delete the constructor that takes const Foo&& as a parameter. Note that I need the const since otherwise someone may return const Foo from a function, and in that case it will bind to the const Foo& constructor.
Question:
Is this the correct paradigm of disable rvalue binding? Am I missing something?
According to §7.1.5.1/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.
So my question becomes: when is an object a const object?
In particular, is a const member in a non-const object considered a const object?
class Foo {
const Bar bar;
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
}
This comes up because I have an immutable class (all fields are const), but I want to have a move constructor, which technically modifies the value passed in. I'm ok with "cheating" in that case, since it doesn't break logical constness.
The simple rule is: it is ok to cast away constness if the original object is not const. So if you have a non-cont object and, say, you pass the const reference to it to a function, it is legal to cast away constness in the function.
In your example the original object is const, so casting constness away is undefined behaviour.
Let us make this a full example:
struct Bar { int x; };
struct Foo {
const Bar bar;
Foo( int x ):bar(x) {}
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
};
now, let us break the world.
int main() {
Foo f(3);
Bar b = {2};
f.replaceBar(b);
std::cout << f.bar.x << "\n";
}
the above can and probably should output 3, because a const object Bar was created with x=3. The compiler can, and should, assume that the const object will be unchanged throughout its lifetime.
Let's break the world more:
struct Bar {
int* x;
Bar(int * p):x(p) {}
~Bar(){ if (x) delete x; }
Bar(Bar&& o):x(o.x){o.x=nullptr;}
Bar& operator=(Bar&& o){
if (x) delete x;
x = o.x;
o.x = nullptr;
}
Bar(Bar const&)=delete;
Bar& operator=(Bar const&)=delete;
};
struct Foo {
const Bar bar;
Foo( int* x ):bar(x) {}
void replaceBar(Bar bar2) {
*(const_cast<Bar *>&bar) = bar2; // Undefined behavior?
}
};
now the same game can result in the compiler deleting something twice.
int main() {
int* p1 = new int(3);
Foo f( p1 );
Bar b( new int(2) );
f.replaceBar(std::move(b));
}
and the compiler will delete p1 once within replaceBar, and should delete it also at the end of main. It can do this, because you guaranteed that f.bar.x would remain unchanged (const) until the end of its scope, then you violated that promise in replaceBar.
Now, this is just things the compiler has reason to do: the compiler can literally do anything once you have modified an object that was declared const, as you have invoked undefined behavior. Nasal demons, time travel -- anything is up for grabs.
Compilers use the fact that some behavior is undefined (aka, not allowed) to optimize.
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?
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.
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*’
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*.