In this example, is the c-style cast to int& followed by an assignment to kind of hack the interface of class A undefined behavior?
class A
{
public:
A()
: x(0)
{
}
~A()
{
std::cout << x << std::endl;
}
const int& getX()
{
return x;
}
private:
int x;
};
int main()
{
A a;
int& x = (int&)a.getX();
x = 17;
std::cout << x << std::endl;
}
Output:
17
17
If so, what part of the standard can i refer to? Also, is there any reason why this compiles without warnings? (i tested with c++14 on cpp.sh with -Wall, -Wextra and -Wpedantic)
const int& getX() { return x; }
Since this method is not marked const, x is a mutable int. A reference is taken and cast to a const int& at the point of return. Note that although the reference is to a const int, the actual referee int is mutable. This is important.
int& x = (int&)a.getX();
This line takes the returned const int reference and const_cast's it to an int reference. This is legal in c++, full stop. [expr.const.cast]
However, writing through this reference is only legal if the original object being referenced is mutable.
In this case, it is.
You will find the details in [dcl.type.cv]
Related
I am working on const-correctness of my code and just wondered why this code compiles:
class X
{
int x;
int& y;
public:
X(int& _y):y(_y)
{
}
void f(int& newY) const
{
//x = 3; would not work, that's fine
y = newY; //does compile. Why?
}
};
int main(int argc, char **argv)
{
int i1=0, i2=0;
X myX(i1);
myX.f(i2);
...
}
As far as I understand, f() is changing the object myX, although it says to be const. How can I ensure my compiler complains when I do assign to y? (Visual C++ 2008)
Thank a lot!
Because you are not changing any variable in X. Actually, you are changing _y which is an outsider with respect to your class. Don't forget that:
y = newY;
Is assigning the value of newY to the variable pointed by y, but not the references them selves. Only on initialization the references are considered.
The situation is similar to pointer members. In a const member function, the const applies to the pointer itself, not the pointee.
It's the difference between:
X* const //this is how the const applies: you can modify the pointee
const X*
Except X& const isn't valid syntax, since the reference can't be made to refer to another object in the first place (they are implicitly always const). In conclusion: const on methods has no effect on member references.
As additional information for the accepted answer, I want to say, in fact one can
change variables in X.
Because you are not changing any variable in X.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Bar {
public:
void funcA() const {
c++;
}
int b = 3;
int &c = b;
};
int main()
{
Bar b;
b.funcA();
cout << b.b << endl; //4
}
So the main idea for this problem is:
It modifies what the member refers to, it does not modify the member.
This also applies for pointer members.
See here Why can reference members be modified by const member functions?
void foo(const int& v) {
int x = v;
std::cout << x;
}
int main()
{
unsigned y = 1;
foo(y);
}
Is passing y in place of a const int& legal in C++
There are two factors that allow your code to work. First, function arguments are allowed up to one implicit conversion if it would allow them to match an overload. Second, const references can bind to temporaries. What is happening here is y is implicitly converted to int, creating a temporary copy. v is then bound to that temporary.
Consider the following example :
#include <iostream>
void foo(const unsigned int & v) {
std::cout << &v << '\n';
}
void bar(const int & v) {
std::cout << &v << '\n';
}
int main()
{
unsigned int y = 1;
std::cout << &y << '\n';
foo(y);
bar(y);
return 0;
}
You will find that foo(y) prints the same address as y where as bar(y) prints a different address. This won't work with non-const references. Notably, if you could, it would mean that changing v might not actually change y.
Yes, this this can be quite annoying. Compilation passes due to an implicit conversion of y to an anonymous temporary int at the calling site, and a const int& binding is allowed.
You can defeat this by writing
void foo(unsigned v) = delete;
or even
template<typename Y> void foo(Y v) = delete;
so all overloads other than the one you have explicitly given are deleted.
I found in a C++ book the following:
Although we will not be doing it in this book, you can overload a
function name (or operator) so that it behaves differently when used
as an l-value and when it is used as an r-value. (Recall that an
l-value means it can be used on the left-hand side of an assignment
statement.) For example, if you want a function f to behave
differently depending on whether it is used as an l-value or an
r-value, you can do so as follows:
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
I tried this and it didn't work:
class Foo {
public:
int& id(int& a);
const int& id(int& a) const;
};
int main() {
int a;
Foo f;
f.id(a) = 2;
a = f.id(a);
cout << f.id(a) << endl;
}
int& Foo :: id(int& a) {
cout << "Bar\n";
return a;
}
const int& Foo :: id(int& a) const {
cout << "No bar !\n";
return a;
}
Have I wrongly understood it ?
Either the book's example is flat-out wrong, or you copied the wrong example from the book.
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
With this code, when you call s.f() where s is an object of type SomeClass, the first version will be called when s is non-const, and the second version will be called when s is const. Value category has nothing to do with it.
Ref-qualification looks like this:
#include <iostream>
class SomeClass {
public:
int f() & { std::cout << "lvalue\n"; }
int f() && { std::cout << "rvalue\n"; }
};
int main() {
SomeClass s; s.f(); // prints "lvalue"
SomeClass{}.f(); // prints "rvalue"
}
Ofcourse the book is correct. Let me explain the workings of an example of what the author meant :
#include <iostream>
using namespace std;
class CO
{
int _m;
public:
CO(int m) : _m(m) {}
int& m() { return _m; } // used as an l-value
int const& m() const { return _m; } // used as an r-value
};
int main()
{
CO a(1);
cout << a.m() << endl;
a.m() = 2; // here used as an l-value / overload resolution selects the correct one
cout << a.m() << endl;
return 0;
}
Output is
1
2
What you misunderstood is the function signature. You see when you have an argument &arg (as in id(&arg)) you pretty much predefine the l-valuness of it, so returning it through a const or non const member function does not change a thing.
The author refers to a common writting style that allows for 'getters' and 'setters' to be declared with a signature different only in const qualifires yet compile and behave correctly.
Edit
To be more pedantic, the following phrase
Recall that an l-value means it can be used on the left-hand side of an assignment statement.
is not valid anymore. lr valuness applies to expressions, and the shortest way to explain it, is that an expression whose adress we can take, is an l-value; if it's not obtainable it's an r-value.
So the syntax to which the author refers to, enforces the member function to be used correctly (correct compilation / overload resolution) at both sides of the assignment operator. This nowdays is no longer relevant to lr valueness.
A const member function can only be called on a const object. It makes no difference what you do with the return value. In your example, f is non-const, so it always calls the non-const version of f(). Note that you can also overload on r-value references (&&) in C++11.
In this code the object bar is an const type, but through the const function I still can modify the member x's value. Is that unreasonable?
the output is
15
25
// overloading members on constness
#include <iostream>
using namespace std;
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
int& get() const {return x;}
int& get() {return x;}
};
int main() {
MyClass foo (10);
const MyClass bar (20);
foo.get() = 15;
bar.get() = 25;
cout << foo.get() << '\n';
cout << bar.get() << '\n';
return 0;
}
int& get() const {return x;}
is returning a non-const reference to a member of a const object. (We know that *this is const because of the declaration of int& get() as const.) That should be flagged as an error, since it is an invalid conversion (x is not declared mutable); both gcc and clang will do so. The fact that your compiler only produced a warning is odd, but nonetheless you should heed the warning.
You could avoid the error by explicitly using const_cast<int&>(x), but it would be undefined behaviour (UB) to attempt to use the returned int& to modify x. However, the compiler is not obliged to flag as error or even detect all possible expressions which might produce undefined behaviour.
In short, you're allowed to shoot yourself in the foot, but a good compiler will at least warn you before you do that. Listen to the warnings.
I'm implementing some C++ static analysis rules, and one of them prohibits a function from returning a reference or pointer to a reference parameter of the function, i.e. the following are all non-compliant:
int *f(int& x) { return &x; } // #1
const int *g(const int& x) { return &x; } // #2
int& h(int& x) { return x; } // #3
const int& m(const int& x) { return x; } // #4
The justification given for this is that "It is implementation-defined behaviour whether the reference parameter is a temporary object or a reference to the parameter."
I'm puzzled by this, however, because stream operators in C++ are written in this way, e.g.
std::ostream& operator<<(std::ostream& os, const X& x) {
//...
return os;
}
I think I'm pretty confident that stream operators in C++ do not in general exhibit implementation-defined behaviour, so what's going on?
According to my understanding as it is at present, I would expect #1 and #3 to be well-defined, on the basis that temporaries cannot be bound to non-const references, so int& x refers to a real object that has lifetime beyond the scope of the function, hence returning a pointer or reference to that object is fine. I would expect #2 to be dodgy, because a temporary could have been bound to const int& x, in which case trying to take its address would seem a bad plan. I'm not sure about #4 - my gut feeling is that that's also potentially dodgy, but I'm not sure. In particular, I'm not clear on what would happen in the following case:
const int& m(const int& x) { return x; }
//...
const int& r = m(23);
As you say, #1 and #3 are fine (though #1 is arguably bad style).
#4 is dodgy for the same reason #2 is; it allows propagating a const reference to a temporary past its lifetime.
Let's check:
#include <iostream>
struct C {
C() { std::cout << "C()\n"; }
~C() { std::cout << "~C()\n"; }
C(const C &) { std::cout << "C(const C &)\n"; }
};
const C &foo(const C &c) { return c; }
int main() {
const C &c = foo(C());
std::cout << "c in scope\n";
}
This outputs:
C()
~C()
c in scope
In C++11, #2 and #4 can be made safe if there are also rvalue reference overloads. Thus:
const int *get( const int &x ) { return &x; }
const int *get( const int &&x ) { return nullptr; }
void test() {
const int x = 0;
const int *p1 = get( x ); // OK; p1 is &x.
const int *p2 = get( x+42 ); // OK; p2 is nullptr.
}
So although they are dodgy, they do have safe uses if the programmer knows what they are doing. It'd be draconian to forbid this.
(Perhaps safer would be if the const rvalue reference overload was made private, left undefined, or otherwise caused a compile-time or link-time error. This is especially true for the #4 case, where we return a reference but there is nothing good to return a reference to and the language doesn't allow references to null.)