Why isn't discarding const qualifier allowed? Suppose we wrote:
#include <iostream>
struct A
{
void operator=(const A&){ std::cout << "A&" << std::endl; }
void operator=(const A&&){ std::cout << "A&&" << std::endl; }
};
const A a;
A b;
int main()
{
a = b; //Error: discarding qualifier
}
Couldn't someone provide a reference where the Standard disallows that?
The problem there is that a is const and therefore operator=, which is supposed to modify the object on which is called, is disallowed. This is caused by const-correctness.
Declaring operator= to be const would not make sense, because the semantic of operator= are that it should modify the object on which it's called with the right hand side value and return a T& reference to the left hand side object, which is not possible if the left hand side object is const (excluding const_cast usage).
On the other hand, the following is allowed:
int main()
{
A b;
const A a = b;
}
because in that case it's construction of a new constant object.
As you said, the standard specifies this at §9.3.2/3 with the following wording:
A cv-qualified member function can be called on an object-expression (5.2.5) only if the object-expression is as cv-qualified or less-cv-qualified than the member function.
Related
What is the meaning of const in declarations like these? The const confuses me.
class foobar
{
public:
operator int () const;
const char* foo() const;
};
When you add the const keyword to a method the this pointer will essentially become a pointer to const object, and you cannot therefore change any member data. (Unless you use mutable, more on that later).
The const keyword is part of the functions signature which means that you can implement two similar methods, one which is called when the object is const, and one that isn't.
#include <iostream>
class MyClass
{
private:
int counter;
public:
void Foo()
{
std::cout << "Foo" << std::endl;
}
void Foo() const
{
std::cout << "Foo const" << std::endl;
}
};
int main()
{
MyClass cc;
const MyClass& ccc = cc;
cc.Foo();
ccc.Foo();
}
This will output
Foo
Foo const
In the non-const method you can change the instance members, which you cannot do in the const version. If you change the method declaration in the above example to the code below you will get some errors.
void Foo()
{
counter++; //this works
std::cout << "Foo" << std::endl;
}
void Foo() const
{
counter++; //this will not compile
std::cout << "Foo const" << std::endl;
}
This is not completely true, because you can mark a member as mutable and a const method can then change it. It's mostly used for internal counters and stuff. The solution for that would be the below code.
#include <iostream>
class MyClass
{
private:
mutable int counter;
public:
MyClass() : counter(0) {}
void Foo()
{
counter++;
std::cout << "Foo" << std::endl;
}
void Foo() const
{
counter++; // This works because counter is `mutable`
std::cout << "Foo const" << std::endl;
}
int GetInvocations() const
{
return counter;
}
};
int main(void)
{
MyClass cc;
const MyClass& ccc = cc;
cc.Foo();
ccc.Foo();
std::cout << "Foo has been invoked " << ccc.GetInvocations() << " times" << std::endl;
}
which would output
Foo
Foo const
Foo has been invoked 2 times
The const means that the method promises not to alter any members of the class. You'd be able to execute the object's members that are so marked, even if the object itself were marked const:
const foobar fb;
fb.foo();
would be legal.
See How many and which are the uses of “const” in C++? for more information.
The const qualifier means that the methods can be called on any value of foobar. The difference comes when you consider calling a non-const method on a const object. Consider if your foobar type had the following extra method declaration:
class foobar {
...
const char* bar();
}
The method bar() is non-const and can only be accessed from non-const values.
void func1(const foobar& fb1, foobar& fb2) {
const char* v1 = fb1.bar(); // won't compile
const char* v2 = fb2.bar(); // works
}
The idea behind const though is to mark methods which will not alter the internal state of the class. This is a powerful concept but is not actually enforceable in C++. It's more of a promise than a guarantee. And one that is often broken and easily broken.
foobar& fbNonConst = const_cast<foobar&>(fb1);
These const mean that compiler will Error if the method 'with const' changes internal data.
class A
{
public:
A():member_()
{
}
int hashGetter() const
{
state_ = 1;
return member_;
}
int goodGetter() const
{
return member_;
}
int getter() const
{
//member_ = 2; // error
return member_;
}
int badGetter()
{
return member_;
}
private:
mutable int state_;
int member_;
};
The test
int main()
{
const A a1;
a1.badGetter(); // doesn't work
a1.goodGetter(); // works
a1.hashGetter(); // works
A a2;
a2.badGetter(); // works
a2.goodGetter(); // works
a2.hashGetter(); // works
}
Read this for more information
Blair's answer is on the mark.
However note that there is a mutable qualifier which may be added to a class's data members. Any member so marked can be modified in a const method without violating the const contract.
You might want to use this (for example) if you want an object to remember how many times a particular method is called, whilst not affecting the "logical" constness of that method.
Meaning of a Const Member Function in C++ Common Knowledge: Essential Intermediate Programming gives a clear explanation:
The type of the this pointer in a non-const member function of a class
X is X * const. That is, it’s a constant pointer to a non-constant X
(see Const Pointers and Pointers to Const [7, 21]). Because the object
to which this refers is not const, it can be modified. The type of
this in a const member function of a class X is const X * const. That
is, it’s a constant pointer to a constant X. Because the object to
which this refers is const, it cannot be modified. That’s the
difference between const and non-const member functions.
So in your code:
class foobar
{
public:
operator int () const;
const char* foo() const;
};
You can think it as this:
class foobar
{
public:
operator int (const foobar * const this) const;
const char* foo(const foobar * const this) const;
};
I would like to add the following point.
You can also make it a const & and const &&
So,
struct s{
void val1() const {
// *this is const here. Hence this function cannot modify any member of *this
}
void val2() const & {
// *this is const& here
}
void val3() const && {
// The object calling this function should be const rvalue only.
}
void val4() && {
// The object calling this function should be rvalue reference only.
}
};
int main(){
s a;
a.val1(); //okay
a.val2(); //okay
// a.val3() not okay, a is not rvalue will be okay if called like
std::move(a).val3(); // okay, move makes it a rvalue
}
Feel free to improve the answer. I am no expert
when you use const in the method signature (like your said: const char* foo() const;) you are telling the compiler that memory pointed to by this can't be changed by this method (which is foo here).
Here const means that at that function any variable's value can not change
class Test{
private:
int a;
public:
void test()const{
a = 10;
}
};
And like this example, if you try to change the value of a variable in the test function you will get an error.
The const keyword used with the function declaration specifies that it is a const member function and it will not be able to change the data members of the object.
https://isocpp.org/wiki/faq/const-correctness#const-member-fns
What is a "const member function"?
A member function that inspects (rather than mutates) its object.
A const member function is indicated by a const suffix just after the member function’s parameter list. Member functions with a const suffix are called “const member functions” or “inspectors.” Member functions without a const suffix are called “non-const member functions” or “mutators.”
class Fred {
public:
void inspect() const; // This member promises NOT to change *this
void mutate(); // This member function might change *this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
changeable.inspect(); // Okay: doesn't change a changeable object
changeable.mutate(); // Okay: changes a changeable object
unchangeable.inspect(); // Okay: doesn't change an unchangeable object
unchangeable.mutate(); // ERROR: attempt to change unchangeable object
}
The attempt to call unchangeable.mutate() is an error caught at compile time. There is no runtime space or speed penalty for const, and you don’t need to write test-cases to check it at runtime.
The trailing const on inspect() member function should be used to mean the method won’t change the object’s abstract (client-visible) state. That is slightly different from saying the method won’t change the “raw bits” of the object’s struct. C++ compilers aren’t allowed to take the “bitwise” interpretation unless they can solve the aliasing problem, which normally can’t be solved (i.e., a non-const alias could exist which could modify the state of the object). Another (important) insight from this aliasing issue: pointing at an object with a pointer-to-const doesn’t guarantee that the object won’t change; it merely promises that the object won’t change via that pointer.
In const objects only const methods can be called. All fielnd in such a method considered as const field.
Last issue has curious effect:
pointer becomes a const pointer int* const, which is not the same as a pointer to const const int*. Thus you can alter the object which pointer points to, but can't make pointer to point to another object.
reference should become a const reference, but it is invariantly a const reference: you can not re-init it to another object. But again you can alter the object which reference refers to.
1. In global scope, this gives error: redefinition of 'f'
#include <iostream>
using namespace std;
void f(int x) { cout << "f" << endl; }
void f(const int x) { cout << "f (const)" << endl; } // error: redefinition of 'f'
int main() { }
2. Defining two copy constructors (one with const, the other without) compiles
#include <iostream>
using namespace std;
class Foo {
public:
Foo(const Foo&) { cout << "copy (const)" << endl; }
Foo(Foo&) { cout << "copy" << endl; }
};
int main() { }
Question
Why is #1 a redefinition error but #2 is not?
For the second example, is there a use case for defining two copy constructors (one with const the other without)?
Only the top-level constness is ignored on parameters when checking if two functions are the same.
What does "top-level" constness mean? It means that something is actually const, as reported by std::is_const_v.
For example int *const is top-level const (because the pointer itself is const), and const int * is not (because the pointer itself is not const, even though it points to something that is const).
Something can be const at several levels, e.g. const int *const.
const int is also const at the top level, because there's only one "level" here.
If you have more than one star (e.g. int ***), then the type is top-level const only if const is placed after the rightmost star.
So, const int is const at the top level, meaning const int and int only differ in top-level constness.
But (similarly to const int *) const Foo& is const not at the top-level. It's a non-const reference to const Foo. (The references can never be const1, e.g. Foo &const doesn't compile.)
So the difference between Foo & and const Foo & is not on the top level, making Foo(Foo &) and Foo(const Foo &) different constructors.
1 Some argue that all references are effectively const because you can't make them point to a different object after they're created. But the language says they're not const, and std::is_const_v returns false for them.
There is a fundamental difference between the two.
One is an overload between int and const int. It's a value type. There is no semantic difference for the caller, the effect of const only affects the body of the function.
void f(int);
int a = 1;
const int b = 2;
f(a); // must copy the int value into the argument
f(b); // same thing.
The other is a const vs a mutable reference. It has a difference for the caller.
void f(int&);
void f(const int&);
int a = 1;
const int b = 2;
f(a); // could call f(int&) or f(int const&), but the mutable is a more closely match
f(b); // can only call f(int const&);
Since its passed by reference, the constness matter for the caller of the function. A function that tries to mutate a const object by reference must be invalid, and a non const object should be passed to the non const overload by default.
With only values, it don't matter at all. It is a new object. No matter the qualifier, it has no meaning for the caller, and it should therefore not care since it only affect the implementation.
You can even add const in definitions only if you want, since it declares the same function:
void f(int);
int main() {
f(1);
}
void f(const int a) {
std::cout << "hello " << a << std::endl;
}
Live example
As for your second question, I would say that since the addition of rvalue reference, there is little need for a copy constructor to take by mutable reference.
For example, std::auto_ptr used to have a constructor that took a mutable reference to transfer ownership, but it created all sorts of problems. But it has been completely replaced by std::unique_ptr, which uses rvalue reference to transfer ownership.
Rvalue reference ensure that you don't care for the integrity of the copied-from object, and that it's okay to steal off resources from it.
#1 is a redefinition error because even if you modify the local x it is passed by value so after the return call the value will stay the same.
I got to know that to make a friend function, friend function should be explicitly declared in enclosing scope or take an argument of its class. However, this one seems to be a caveat, I am failed to understand. Why does the call to f1(99) not works?
class X {
public:
X(int i) {
std::cout << "Ctor called" << std::endl;
}
friend int f1(X&);
friend int f2(const X&);
friend int f3(X);
};
int f1(X& a) {
std::cout << "non-const called" << std::endl;
}
int f2(const X& a) {
std::cout << "const called" << std::endl;
}
int f3(X a) {
std::cout << "object called" << std::endl;
}
int main() {
f1(99);
f2(99);
f3(99);
}
You're calling the functions with argument 99; but all the functions expect an X. 99 could convert to X implicitly, but the converted X is a temporary object and can't be bound to lvalue-reference to non-const.
The temporary object could be bound to lvalue-reference to const (and also rvalue-reference since C++11), then f2(99); works. And it could be copied to the parameter of f3, then f3(99) works too.
The effects of reference initialization are:
Otherwise, if the reference is lvalue reference to a non-volatile const-qualified type or rvalue reference (since C++11):
Otherwise, object is implicitly converted to T. The reference is bound to the result of the conversion (after materializing a temporary) (since C++17). If the object (or, if the conversion is
done by user-defined conversion, the result of the conversion
function) is of type T or derived from T, it must be equally or less
cv-qualified than T, and, if the reference is an rvalue reference, must not be an lvalue (since C++11).
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.
struct B
{
};
struct A
{
operator A&() const;
operator B&() const;
};
int main()
{
const A a;
B& br = a;
A& ar = a;
}
Why can I create cast operator to B&, but not to A&.
May be it does not have much sense (one can use it to erase const modifier, as in example), but it at least inconsistent!
You can't do this because it's explicitly forbidden. N3290 § 12.3.2 states:
Such functions are called
conversion functions. No return type can be specified. If a conversion function is a member function, the
type of the conversion function (8.3.5) is “function taking no parameter returning conversion-type-id”. A
conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified)
same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to
it), or to (possibly cv-qualified) void.
(Emphasis mine)
This is discussed further in a note:
These conversions are considered as standard conversions for the purposes of overload resolution (13.3.3.1, 13.3.3.1.4) and
therefore initialization (8.5) and explicit casts (5.2.9).
Which explains this decision - it would interfere with the built-in mechanics too much. (For little gain).
If you really want something non-const from a const object the only smart way to do this is constructing a new instance using the copy constructor.
As a work around you could introduce a lightweight intermediary (like a smart pointer):
struct B {};
struct A {};
namespace {
B b_inst;
A a_inst;
}
struct A_wrapper {
A& inst;
// This is perfectly fine: const alters the reference, not what it refers to
operator A&() const { return inst; }
operator B&() const { return b_inst; }
A_wrapper() : inst(a_inst) {}
};
int main() {
const A_wrapper a;
B& br = a;
A& ar = a;
}
But really, wanting to do this in the first place looks like a code smell.
The proper way to do this would be to use const_cast.
For example,
#include <iostream>
using namespace std;
void f(int* p) {
cout << *p << endl;
}
int main(void) {
const int a = 10;
const int* b = &a;
// Function f() expects int*, not const int*
// f(b);
int* c = const_cast<int*>(b);
f(c);
// Lvalue is const
// *b = 20;
// Undefined behavior
// *c = 30;
int a1 = 40;
const int* b1 = &a1;
int* c1 = const_cast<int*>(b1);
// Integer a1, the object referred to by c1, has
// not been declared const
*c1 = 50;
return 0;
}
Declaring a conversion to a reference to self is not ill-formed. Your problem comes at the time where your reference is initialized. As the type of the reference and the type of the initialization expression are the same, the reference is bound directly and your user defined conversion operator is never considered. Thus normal conversion rules apply and const conversion makes the code ill-formed.
Anyway, what your are doing is basically asking yourself to get shot in the foot. If you don't like constness, don't use it. If you do it consistently, it will never bother you, but it is not going to make you new friends.