When I was writing an overide function with const parameter instead of non-const parameter, I thought the compiler would report an error, because the base function has non-const parameter, but it succeeded to compile it. Why?
My code:
#include <iostream>
class A
{
public:
virtual uint64_t cal(uint64_t value)
{
std::cout << value << std::endl;
return value;
}
};
class B : public A
{
public:
uint64_t cal(const uint64_t value) override;
};
uint64_t B::cal(const uint64_t value)
{
std::cout << value + 1 << std::endl;
return (value+1);
}
int main()
{
B b;
b.cal(1);
return 0;
}
Why?
Because the top const qualifier of a function argument is ignored in a declaration.
uint64_t cal(uint64_t value);
and
uint64_t cal(const uint64_t value);
declare the exact same type of function. cal is a function taking a uint64_t and returning a uint64_t. The const qualifier makes no difference for calling code, since value is always a copy of the passed in argument.
The only difference is in the function body, where the const qualifier will prevent you from modifying the paraemter. But that is an implementation detail.
This difference can even raise coding style questions. For instance see
Is it better to remove "const" in front of "primitive" types used as function parameters in the header?
Note that top-level cv-qualifiers are dropped when considering function type, so uint64_t cal(uint64_t) and uint64_t cal(const uint64_t) are considered as the same function type.
(emphasis mine)
The type of each function parameter in the parameter list is determined according to the following rules:
4) Top-level cv-qualifiers are dropped from the parameter type (This adjustment only affects the function type, but doesn't modify the property of the parameter: int f(const int p, decltype(p)*); and int f(int, const int*); declare the same function)
Top level const of parameters is not a part of the signature of the function. The only requirement for overloading is that the signatures must match.
#include <type_traits>
int main()
{
static_assert(std::is_same_v<int(int), int(const int)>);
}
Compiles fine
Related
I am a bit confused about some warnings I get when compiling my C++11 code using mingw64. This is my MWE:
class A{
const string name;
const int ID;
public:
A(string name_, int ID_) : name(name_), ID(ID_){
// initialize non-const members
}
const string getName() const{return name;}
const int getID() const{return ID;}
};
int main()
{
A aObj = A("Aname", 1);
std::cout << "getName() = " << aObj.getName() << std::endl;
std::cout << "getID() = " << to_string(aObj.getID()) << std::endl;
}
Code executes fine and does what it should, but I get this compiler warning:
,,localtest.cpp:10:9: warning: type qualifiers ignored on function return type
[-Wignored-qualifiers]
const int getID() const{return ID;}
So the warning only shows for getID() but not for getName(), even though both have the same type qualifiers. Can somebody explain to me, why this warning seems only to show for string but not for int? I suppose it has something to do with int being a primitive data type - but what exactly?
std::string is a class that has member functions that can be constant. If you have a constant object of the class you may apply only constant member functions.
As for fundamental types like for example int then the qualifier const does not make a sense for a return value because in any case you can not change the returned value.
Here is a demonstrative program
#include <iostream>
#include <string>
template <typename T>
const T f( const T &t )
{
return t;
}
int main()
{
std::cout << f( std::string( "Hello World!" ) ).length() << '\n';
// Invalid assignment
// f( 10 ) = 20;
return 0;
}
The program output is
12
As you can see you can apply constant member functions to the returned object of the type std::string (but you can not apply non-constant member functions). And you can not change the returned value of the type int.
Consider the following:
struct MyType {
void foo() const;
void bar();
};
MyType getMutable();
const MyType getConst();
int main() {
getMutable().foo(); // fine
getMutable().bar(); // fine
getConst().foo(); // fine
getConst().bar(); // Not allowed!
}
There just isn't anything equivalent for int. The set of operations you can do on a int RValue is the exact same as for a const int RValue. That's why you are getting a redundancy warning.
See [expr.type]:
If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
The essence is: you can have expressions of const class or array types, but not of const primitive types.
Since int is not a class, it is enough for return type to be rvalue to prevent any and all modifications of the returned object. Thus,
getInt(20) = 500;
would not be compilable code, and there are no members you could invoke on objects of int type. This is why const-qualifying built-in types as return values make no sense, and compiler is warning you about that.
But the situation is different for the classes.
getString("string").clear();
Might be either valid or invalid code, based on whether getString returns non-const or const std::string object, thus compiler is not issuing a warning in the latter case.
This question already has answers here:
Top-level const doesn't influence a function signature
(7 answers)
const qualifier disappears from pure virtual function [duplicate]
(1 answer)
Closed 4 years ago.
A C++ guru told that varying the function parameter type with const in derived class, will break the virtual call mechanism.
I tried a simple program ( forgive for non standard code, written purely for test) , which prove otherwise.
The function parameter change by const value will not break the virtual mechanism,
Is there any reasons & documentation pointing to this behavior?
Behavior noted with VS 2012 compiler & latest g++ compiler.
#include <iostream>
using namespace std;
class Base
{
public:
Base(){ cout<<"base"<<endl;}
virtual ~Base(){ cout<<"dest base"<<endl;}
virtual void test(const int x){ cout << "base test"<<"x = " << x<<endl;}
};
class Derived : public Base
{
public:
Derived(){ cout<<"derived"<<endl;}
virtual ~Derived(){ cout<<"dest derived"<<endl;}
virtual void test(int x){ cout << "derived test"<<"x = " << x<<endl;}
};
int main() {
Base *b = new Derived();
b->test(10);
delete b;
return 0;
}
output:
base
derived
derived testx = 10
dest derived
dest base
The top level cv-qualifier is not part of function signature, they are simply ignored.
[dcl.fct]/5
After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.
Your C++ guru is wrong (provided you understood them, gurus tend to talk in cryptic messages). Const-qualifier on the argument type itself is not a part of a function signature at all.
For example, void foo(int* const ); is no different from void foo(int* ). Please note, it is not the same as const qualification of the indirect object, like void foo(const int* ) is different from void foo(int* ).
In your particular case, void test(int x) is the same as void test(int const x)
void test(int) != void test(int) const and would "break" virtual call.
and void test(int&) != void test(const int&) and would "break" virtual call.
void test(int) is the same declaration than void test(const int) and won't "break" virtual call.
The equivalent of std::decay happens to argument types for functions. (It's actually the reverse, std::decay is modeled after what functions arguments do.)
The outermost const will be dropped from the signature. By outermost, think of types as envelopes composed over different types. A pointer to const int is a different type than a pointer to int, and will result in a different function signature. (With a pointer, you can think of the pointer itself as the outer thing, and what it points to is "inner" and not modified.)
const int - becomes just int
int * is unchanged and remains int *
const int * is unchanged and remains const int * - const is on the int, not the pointer, and only the outermost const is dropped
int const * is unchanged and remains int const * - const is on the int, not the pointer, and only the outermost const is dropped. (Note, this is 100% identical in meaning as const int *)
int * const - changes to int * - because const qualifies the outermost pointer
int * const * const becomes int * const * - outer const is dropped on outer pointer, inner const is not dropped on the inner pointer.
const int * const * const becomes const int * const * - outer const is dropped on outer pointer, inner const is not dropped on the inner pointer, and const is also kept on the internal-most int
MyTemplate<const T> - unchanged, remains MyTemplate<const T>, because the const isn't on the outer type, but nestled in a template parameter
So yes, const does affect type, but not in the simple case like you tried. Only when it's contained inside of a type, not affecting the outermost type.
If you read types from right-to-left it can help. If the rightmost thing in a type specifier is a const, it is always dropped (example int * const). If the leftmost thing is const, it is only dropped if the thing it is qualifying is the rightmost thing in the type (example const int, the leftmost thing is const and it affects the int to its right AND the int to its right is the rightmost thing in the type.) (example 2: const * int not dropped, because leftmost const modifies the thing to its right that is not the rightmost thing in the type.)
He was right.just found it by seeing the warning.
In this case : Compilers before VS2008 would break the virtual mechanism in this case.
Later compilers it gives warning C4373: virtual function overrides '%$pS', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers
found the documentation here https://msdn.microsoft.com/en-us/library/bb384874.aspx
This question already has answers here:
Top-level const doesn't influence a function signature
(7 answers)
const qualifier disappears from pure virtual function [duplicate]
(1 answer)
Closed 4 years ago.
A C++ guru told that varying the function parameter type with const in derived class, will break the virtual call mechanism.
I tried a simple program ( forgive for non standard code, written purely for test) , which prove otherwise.
The function parameter change by const value will not break the virtual mechanism,
Is there any reasons & documentation pointing to this behavior?
Behavior noted with VS 2012 compiler & latest g++ compiler.
#include <iostream>
using namespace std;
class Base
{
public:
Base(){ cout<<"base"<<endl;}
virtual ~Base(){ cout<<"dest base"<<endl;}
virtual void test(const int x){ cout << "base test"<<"x = " << x<<endl;}
};
class Derived : public Base
{
public:
Derived(){ cout<<"derived"<<endl;}
virtual ~Derived(){ cout<<"dest derived"<<endl;}
virtual void test(int x){ cout << "derived test"<<"x = " << x<<endl;}
};
int main() {
Base *b = new Derived();
b->test(10);
delete b;
return 0;
}
output:
base
derived
derived testx = 10
dest derived
dest base
The top level cv-qualifier is not part of function signature, they are simply ignored.
[dcl.fct]/5
After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.
Your C++ guru is wrong (provided you understood them, gurus tend to talk in cryptic messages). Const-qualifier on the argument type itself is not a part of a function signature at all.
For example, void foo(int* const ); is no different from void foo(int* ). Please note, it is not the same as const qualification of the indirect object, like void foo(const int* ) is different from void foo(int* ).
In your particular case, void test(int x) is the same as void test(int const x)
void test(int) != void test(int) const and would "break" virtual call.
and void test(int&) != void test(const int&) and would "break" virtual call.
void test(int) is the same declaration than void test(const int) and won't "break" virtual call.
The equivalent of std::decay happens to argument types for functions. (It's actually the reverse, std::decay is modeled after what functions arguments do.)
The outermost const will be dropped from the signature. By outermost, think of types as envelopes composed over different types. A pointer to const int is a different type than a pointer to int, and will result in a different function signature. (With a pointer, you can think of the pointer itself as the outer thing, and what it points to is "inner" and not modified.)
const int - becomes just int
int * is unchanged and remains int *
const int * is unchanged and remains const int * - const is on the int, not the pointer, and only the outermost const is dropped
int const * is unchanged and remains int const * - const is on the int, not the pointer, and only the outermost const is dropped. (Note, this is 100% identical in meaning as const int *)
int * const - changes to int * - because const qualifies the outermost pointer
int * const * const becomes int * const * - outer const is dropped on outer pointer, inner const is not dropped on the inner pointer.
const int * const * const becomes const int * const * - outer const is dropped on outer pointer, inner const is not dropped on the inner pointer, and const is also kept on the internal-most int
MyTemplate<const T> - unchanged, remains MyTemplate<const T>, because the const isn't on the outer type, but nestled in a template parameter
So yes, const does affect type, but not in the simple case like you tried. Only when it's contained inside of a type, not affecting the outermost type.
If you read types from right-to-left it can help. If the rightmost thing in a type specifier is a const, it is always dropped (example int * const). If the leftmost thing is const, it is only dropped if the thing it is qualifying is the rightmost thing in the type (example const int, the leftmost thing is const and it affects the int to its right AND the int to its right is the rightmost thing in the type.) (example 2: const * int not dropped, because leftmost const modifies the thing to its right that is not the rightmost thing in the type.)
He was right.just found it by seeing the warning.
In this case : Compilers before VS2008 would break the virtual mechanism in this case.
Later compilers it gives warning C4373: virtual function overrides '%$pS', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers
found the documentation here https://msdn.microsoft.com/en-us/library/bb384874.aspx
§14.8.2/4 allows the instantiation of two different functions, g<int> and g<const int> from the template definition. Why doesn't the Standard allow the definition of the two functions f in the code below? I know that both functions would have the same type void(int). But that also happens with the instantiated functions g. The note in §14.8.2/4 says: f<int>(1) and f<const int>(1) call distinct functions even though both of the functions called have the same function type..
#include <iostream>
template<typename T>
void g(T t) { std::cout << t << '\n'; }
void f(int i) { std::cout << i << '\n'; }
//void f(int const i) { std::cout << i << '\n'; } // doesn't compile
int main()
{
g<int>(1);
g<int const>(2);
}
Top-level consts on the parameter types are not part of the function signature. So the two versions of f() you've defined are the same function as far as overload resolution is concerned making the second one a redefinition.
From §13.1/3 [over.load]
— Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called. [ Example:
typedef const int cInt;
int f (int);
int f (const int); // redeclaration of f(int)
int f (int) { /* ... */ } // definition of f(int)
int f (cInt) { /* ... */ } // error: redefinition of f(int)
—end example ]
Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.
The fact that top-level const is not part of the function signature allows for a minor advantage.
Suppose you have a function"
void f(int);
in its implementation, if you know you are not going to change the input parameter, you can declare:
void f(int const x) {
std::cout << x << "\n";
}
and this is none of the business of the caller. Later, it turns out it would be useful to munge the input value (say, you want to treat negative integers as 0):
void f(int x) {
if (x<0) x = 0;
std::cout << x << "\n";
}
and without changing the signature or the rest of the body of the function, we are good to go.
Basically, the top level constness of arguments doesn't impact the usual binary calling conventions of C++, and logically the constness is no business of the caller. By eliminating that from the signature, we get some benefit.
For template functions, however, the types impact both the signature and the body of the function, and that body is part of the template's interface. (decltype lets the types of function parameters impact the body, but the body is not part of the interface like a template)
whether it is possible to overload function by const specifier. That is, we have two functions, one constant the other is not, can we say that the constant function overloaded non-const function ?
Yes and No.
It depends on where you put the const specifier.
When defining member functions, this is possible (Yes-part):
int f() { /*code*/ } //invoke this function on non-const object
int f() const { /*code*/ } //ok : invoke this function on const object
Note that in the absence of the first function, even non-const object will invoke the second function (i.e const member function) and in the absence of the second function, you wouldn't be able to invoke the first function on const objects!
But this is not possible (No-part):
int g() { /*code*/ }
const int g() { /*code*/ } //error: redefinition
irrespective of whether they're member functions or free functions.
Per § 13.1 / 2:
It's not possible to put const in return-type to overload:
Function declarations that differ only in the return type cannot be
overloaded.
int func();
const int func(); // Error
It's not possible to put const in parameter-list to overload:
Parameter declarations that differ only in the presence or absence of
const and/or volatile are equivalent.
void func(int x);
void func(const int x); // Error
BUT, it's possible:
const and volatile type-specifiers buried within a parameter type
specification are significant and can be used to distinguish
overloaded function declarations.
void func(int &x);
void func(const int &x); // OK
And, it's possible to put const at end of method declaration to distinguish overloads:
int func();
int func() const; // OK