The following code calls a const method passing a reference to a member, which is then modified.
#include <iostream>
struct A {
int i;
A(int _x) : i(_x) { }
void calc(int& j, const int value) const { j = value; }
void set1() { calc(i, 1); }
};
int main()
{
A a(3);
std::cout << a.i << std::endl;
a.set1();
std::cout << a.i << std::endl;
return 0;
}
The code compiles with gcc 6.4.0, and with clang 5.0.2, with no warnings.
Is the code legal?
The const method calc is able to modify the object, when called from a non-const method.
const qualifier on a member function applies to the *this instance.
In calc(), this is a pointer to const A, but the parameter j is taken by non-const reference, so this is perfectly standard behaviour.
Now, if in calc you tried to assign to this->i, the code would not compile.
void A::calc(const int value) const
{
i = value; // Compilation error here: i is a data member of a const instance
}
In the same way, if set1 was made a const member function, then, the code would not compile (because it would try to bind this->i to a parameter taken by non-const reference)
Sure. Marking the method const just makes *this const, i.e. the function promises not to modify the object by writing through this.
It's still possible to modify the object through other means (assuming they're not marked const as well, such as int& j in your example).
Remember that having a "const pointer" like const Thing* or a "const reference" like const Thing& does NOT mean that the const-qualified object cannot change while you have the pointer/reference. It only means that you can't use that particular pointer/reference as a way of changing it. But there could be other names, pointers, or references that do allow changing it.
A couple of examples:
void f1(const int& arg1, int& arg2) {
std::cout << "arg1 before: " << arg1 << "\n";
arg2 = 4;
std::cout << "arg1 after: " << arg1 << "\n"; // same thing?
}
f1 might look as though it must always print the same value in the "before" and "after" lines. But not if someone passes the same int object to both arguments:
void call_f1() {
int n = 7;
f1(n, n); // Prints before 7, after 4!
}
Or if a function call comes between two uses of a const reference, that can similarly change a variable in some way:
void something_else();
void f2(const int& arg) {
std::cout << "arg before: " << arg << "\n";
something_else();
std::cout << "arg after: " << arg << "\n";
}
int n = 2;
void something_else() { n = 8; }
void call_f2() {
f2(n); // Prints before 2, after 8!
}
So it's true that in your void A::calc(int& j, const int value) const function, the this pointer is const A* const, which means you can't change the A object using the this pointer. But there can still be other ways to change it, like here you have an int& j reference to non-const object. If it so happens that j refers to a subobject of *this, then modifying j is a valid way of modifying the subobject of *this. This is similar to my f1 example above, where arg1 can't be used to change the referenced int, but arg2 can, and if they refer to the same int, this means arg1 has changed.
The case is slightly different when a variable is defined with the const qualifier in the first place. If we write
const A a(3);
then we do get a guarantee that (except during the constructor and destructor), the object can't be changed in any way. The language will usually prevent you from accidentally trying, like with a.set1(), but even if you try const_cast tricks, any actual change would then be undefined behavior.
There is nothing wrong with your code. Declaring a method const merely means that this is const. However, your method does not (directly) modify this or any members of this. Consider this contrived, albeit correct example:
struct foo {
int value;
void modify_const(foo& f) const { f.value = 5; }
};
int main() {
foo f;
f.value = 3;
f.modify_const(f);
}
The method does not modify this, and the parameter is declared as non-const, thus calling f.modify_const(f); on a const f will fail due to the parameter being passed as non-const.
Just shows you are never safe. A const qualifier doesn't guarantee the value can never change.
Try it like this, and you can do really nasty things:
#include <iostream>
class A {
const int i;
void calc(int& j, const int value) const { j = value; }
public:
A(int _x) : i(_x) { }
void set1() const { calc(*const_cast<int*>(&i), 1); }
int getI() const { return i; }
};
int main()
{
const A a(3);
std::cout << a.getI() << std::endl;
a.set1();
std::cout << a.getI() << std::endl;
return 0;
}
Related
class Context {
public:
Context(){
field2values_["age"] = std::vector<int>{1,2,3};
}
const std::vector<int>& field2values(const std::string& field) const {
auto it = field2values_.find(field);
if (it == field2values_.end()) {
return default_ints_;
}
return it->second;
}
private:
std::map<std::string, std::vector<int>> field2values_;
std::vector<int> default_ints_;
};
Context ctx;
std::vector<int> ctx_field2values(const std::string& field) {
return ctx.field2values(field);
}
class Checker {
public:
explicit Checker():
user_field_values_(ctx_field2values),
user_field_values_nc_(ctx_field2values)
{}
void print(){
const auto& values = user_field_values_("age");
std::cout << "size=" << values.size() << std::endl; // unexpected: 18446744073709535740
const auto& values_nc = user_field_values_nc_("age");
std::cout << "size=" << values_nc.size() << std::endl; // expected: 3
}
private:
const std::function<const std::vector<int>&(const std::string&)> user_field_values_;
const std::function<std::vector<int>(const std::string&)> user_field_values_nc_;
};
int main() {
Checker checker;
checker.print();
}
As we all know, const reference to temporary variable will extend the its lifetime. But in the code above, it does not work for user_field_values_ while it works for user_field_values_nc_. I guess this is because the type of user_field_values_ does not match its initialization, namely ctx_field2values. But why is there such a difference? Can anyone explain in principle why this rule (const reference to temporary variable) does not take effect?
Thanks in advance.
It's the same reason the following produces a dangling reference:
int f() {
return 42;
}
const int& invoke_f() {
return f();
}
const auto& e = invoke_f(); // dangling!
Basically, when a temporary appears in a return statement, its lifetime is not extended. It gets destroyed at the end of the return statement. ([class.temporary]/(6.11))
The function call operator of user_field_values_ behaves just like the invoke_f above. It invokes ctx_field2values (which returns a vector<int>), and returns the result as a const vector<int>& -- a dangling reference.
In C++23, std::function will be able to recognize this pattern (by means of std::reference_converts_from_temporary) and reject it. But it requires compiler support, which AFAIK does not exist yet.
Please look at the following code and help me understand:
Why the functionality to return a const alias to a literal like my f2 function exists. I don't understand what the point is.
The difference between f2 and f3 is that const does allow me to put a literal in the return statement, but again why?
Any help in understanding this is appreciated.
#include <iostream>
const int f1(int a)
{
return 15;
}
const int& f2(int a)
{
return 14;
}
int& f3(int a)
{
a = 12;
return a;
}
int main()
{
auto a{ 10 };
auto b = f1(a);
auto c = f2(a);
auto d = f3(a);
std::cout << a << " " << b << " " << c << " " << d << std::endl;
a = 1;
b = 2;
c = 3;
d = 4;
std::cout << a << " " << b << " " << c << " " << d << std::endl;
}
Both f2 and f3 have undefined behaviour. You are returning references to local variables. Those local variables are destroyed when the function ends, and the reference is dangling.
The difference between a const reference, and a non-const reference is that a const reference can bind to both rvalues and lvalues.
For non-const references you have to distinguish between lvalue-reference(int&) and rvalue-reference(int&&).
So using the function signature int&& f2(int a) would also compile, but equally have undefined behaviour.
The main reason this is usefull is because when we pass a reference to a function, the function signature tell us if we are expecting an lvalue or an rvalue. We can also overload both and decide to move/copy depending on what we get.
In the case where we don't care, or if we only want to read from the value we can use a const reference and be able to accept both lvalues and rvalues that are passed in.
void foo(MyClass& mc) {
// We know mc is an lvalue.
// We could copy mc, or modify it if we want to use it as an output parameter.
}
void foo(MyClass&& mc) {
// We know mc is an rvalue.
// We know it would be safe to move from mc in this case.
}
MyClass mc;
foo(mc); // Callsthe first overload
foo(MyClass{}); // Calls the second overload
// The two functions above can be overloaded, so we can make sure we deal
// with both cases in the right way
void foo2(const MyClass& mc) {
// This can be both an rvalue or lvalue.
// We don't really care since the reference
// is const we are only going to read from it.
}
foo2(mc); // Both calls work
foo2(MyClass{});
The b, c and d variables in main are initialized with a copy of what the functions return. No matter if they return a copy, a ref or a const ref.
To keep the attributes of the returned value, let's change the first lines in main:
int main()
{
auto a{ 10 };
auto& b = f1(a); // Does not compile, a ref can't be tied to a r-value
auto& c = f2(a); // Ok, c's type is 'const int&'
auto& d = f3(a); // Ok, d's type is 'int&'
std::cout << a << " " << b << " " << c << " " << d << std::endl;
a = 1;
b = 2;
c = 3; // Does not compile. c is a ref to a const
d = 4;
std::cout << a << " " << b << " " << c << " " << d << std::endl;
}
So, the point is you can return a reference to an internal variable, but now allowing the caller to change it. Doing so (instead of returning a copy), you
avoid the copy
allow the caller to see any later change
Not much sense for the code above, but think of a method inside a class, where the internal variable can be changed in other ways.
Besides the return type, f2 and f3 are not correct, as they return a reference to a not-in-memory (f2) or temporary object (f3).
Let's say you write a function that needs to return a complex object, but this object shouldn't be modified (such as pointer to a shared resource, class-property, some sort a singleton data and so on).
For the sake of this answer, lets assume the type in "struct Point".
You have 2 options to do so:
return it by value, which will create a deep copy of its primitive type members and a shallow copy of its by-reference-types members:
const struct Point f2(...)
return it by reference, which will copy only the pointer to the object:
const struct Point* f2()
const struct Point& f2()
both are valid, while the second one has the advantage when dealing with heavy objects.
In the code you provided you do not see the difference because "int" is a primitive type which means it has known way to be copied. This means var "c" isn't actually an alias nor a const, its an int who took its value from the return type of f2
Is it valid when a function returns by reference its own internal static variable?
const int& f() {
static int n=10;
return n;
}
Yes, there is nothing wrong with this. In particular, the static variable isn't destroyed when the function exits, so it doesn't return a dangling reference (as it would if n was not static).
Just keep in mind that it's a static variable, so for example in this:
#include <iostream>
const int& f(int x) {
static int n;
n = x;
return n;
}
int main() {
const int &a = f(1);
const int &b = f(2);
cout << a << " " << b;
}
a and b refer to the same variable, so this prints "2 2" and not "1 2".
This is fine. The static variable will be initialized the first time the function is executed, and will survive beyond the function's return.
assuming that I have a generic class A
class A {
...
int a; // a member
void foo() const{...} // a member function qualified as const
...
};
this implies that if I declare an instance of A like A k; and then I call k.foo(); the this pointer that is acting on/inside that call to foo is something of type const A * const .
Now I would like to know why the code in this blog post works, especially about why this doesn't apply to global variables.
My explanation is about an hidden operation about pointer aliasing, like the this pointer being copied implicitly and during this copy the result is not const anymore ( for some reason ... ) but it's still a this pointer meaning that is a pointer to the same instance.
My question is about: what really const does if it's applied after the declaration of an interface for a member function ? Do you have a specific answer for the linked blog post ?
code from the blog
#include <iostream>
class counter {
public:
int i;
counter();
int inspect() const;
void increment();
};
counter sigma_inspect; // sigma_inspect is global
counter::counter() { i = 0; }
int counter::inspect() const {
sigma_inspect.increment();
return i;
}
void counter::increment() {
++i;
return;
}
int main(void) {
counter a;
std::cout << a.inspect() << "\n";
std::cout << sigma_inspect.inspect() << "\n";
std::cout << sigma_inspect.inspect() << "\n";
return 0;
}
The call in the blog post is using sigma_inspect which is non-const and it is calling a non-const method on it instead of calling said method through the const this pointer. So what? The author seems to expect magic instead of the obvious of what he wrote. It's like having
T* t = ...;
const T* ct = t;
t->foo(); // foo() is not const, but hey,
// I also have a const pointer now (ct),
// so why can I still use this???
Generally, if someone calls C++ stupid it tells you more about the author instead of the language :)
Is there a way to remove the 'plumb' version of all of my functions, without the need to change the 'hit' line to the 'fixed'?
Yes my program works fine, but I think if is there a way to get ride from this version of all of my functions.
Keep in mind that int is not really int in my programs, but a type alias which can be object ( e.g. container_reference<std::array<double,4>> ) or reference ( e.g. std::array<double,4> & )
void func(int &&m) { cout << "rvalue: " << m << endl; }
void func(int &m) { cout << "lvalue: "; func(std::move(m)); } // PLUMB!
int main()
{
int a = 5;
func(a); // HIT!
func(std::move(a)); // FIXED!
func(6);
func(a + 5);
}
I'm having a bit of trouble understand exactly what you want, but this might be an option:
template<typename T>
void func(T &&m) {
// ...
}
T&& has been dubbed "universal reference" as it will bind to both lvalues and rvalues due to reference collapsing rules.