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
Related
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.
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
Is there any difference between:
void (* const algorithm)();
and
void const (* const algorithm)();
when dealing with const pointers to static methods?
I understand that it would make sense to use const if the pointer points to memory that should not be modified, in case of pointers to variables, as stated in this answer. However, are function addresses not effectively constant during run-time anyway?
The reason I am asking this is, the second option as a function parameter does not work.
EDIT
Here is the code that does not compile.
struct A {
static void a() {}
};
void b(void const (* const callback)()) {}
int main() {
b(&A::a); // No matching function for call to 'b'
}
The above example works, if function a() has a return type const void.
const after (or before) the return type applies to the return type. There is no such thing as a "pointer to const function" as there is no such thing as a const function. (Although, there is such thing as a pointer to const member function, as const member functions do exist. But there the constness applies to the object argument, not to the function itself. There the constness is expressed in the same way as in the declaration of a member function - after the parenthesized parameter list).
There is no difference between void() and void const() in the sense that both functions behave exactly the same because there is no behavioral difference between a const void return type and a non-const void return type. Object cannot be const when there is no object.
I would expect a compiler to issue a warning when a non-class return value is directly qualified with const.
However, void() and void const() are different in the sense that void and void const are technically separate types, as are all const qualified types different from their non-const counterpart. Therefore the function pointers to const returning and non-const returning functions are different function pointer types. As such, the standard won't allow a function pointer to one type be bound to a function of another type.
So, to fix your non-compiling code, simply replace void const which is nonsensical with void in the function pointer.
Start with the "inner" const :
using void_function = void();
void (*p1)() = nullptr; // similar to `void_function* p1 = nullptr;`
void (* const p2)() = nullptr; // similar to `void_function* const p2 = nullptr;`
p2 is constant whereas p1 is mutable.
When moving const, as following:
const void_function* p3 = nullptr; // or void_function const* p3; // -> const has no effect
void (const* p4)() = nullptr; // Invalid syntax
There are no "const function" vs "mutable function".
Now, looking at function return type:
Types void () (function returning void) and const void () (function returning const void !?) are different.
Even if const void make no real sense, it is a valid type.
It might make sense to return const object from function to disallow "direct" modification of the object:
const std::string make_const_string();
make_const_string().push_back('*'); // Doesn't work
std::string s = make_const_string(); // OK, create mutable copy.
So to fix your code:
struct A {
static void a() {}
};
void b(void const (* const callback)()) {}
int main() {
b(&A::a); // No matching function for call to 'b'
}
You have to make &A::a match b's argument type:
static const void a() {}
void b(void (*const callback)())
I suggest second one as const void make no real sense.
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
For a function definition which contains the declaration of type void foo(const int ) Both of the following declarations are valid.
void foo(const int); // valid
void foo(int); // valid, const can be omitted.
But if a function definition contains a declaration of type void foo(const int*) omitting const is ilegal:
void foo(const int *); // valid declaration
void foo(int *); // error: const cannot be omitted.
Why is const cannot be omitted in the function declaration if its parameter has pointer types? What makes the difference?
You can only omit the const specifier when it's applied directly to the parameter. In the pointer case, it's applied to the thing being pointed at, not the pointer itself, so there's an extra level of indirection between const and the parameter. You could omit it in this case:
void foo(int* const);
void foo(int*);
const int x means that x is read-only inside the function. The outside world doesn't care either way; the function is working with a copy of the argument.
However const int *p means that p points to something that is read-only. The outside world does care about this; it's a promise that the pointee can't be modified.
The corollary of this is that the following two declarations are equivalent:
void foo(const int *);
void foo(const int * const);
Function deals with the copy of the passed argument. If that's integer, you can omit const as original will not be touched anyway. In case of the pointer to int type there are three options:
int* p
const int* p (or int const* p)
const int* const p (or int const* const p)
const can be applied to pointed object, pointer itself or to both. What you pass as an argument is pointer so you can omit const for it as its copy is passed to function. So (2) and (3) can be used interchangeably (only when used as function argument type!). But const for pointed object type makes difference:
void foo(const int* p); // function cannot modify pointed integer object through p
void foo(int* p); // function can modify pointed integer object through p
In both cases function can modify p within function, but those changes are not reflected on the original pointer's value.