Consider the following code snippets:
void foo(const int i) // First foo
{
std::cout << "First " << i << endl;
}
void foo(int i) // Second foo
{
std::cout << "Second " << i << endl;
}
int main()
{
int i = 5;
foo(i);
}
Compilation Error:
redefinition of 'void foo(int)'
Since consts can be initialized with non-const objects, the above behaviour seems reasonable. Now consider this:
void foo_ptr(const int* p) // First foo_ptr
{
std::cout << "First " << *p << endl;
}
void foo_ptr(int* p) // Second foo_ptr
{
std::cout << "Second " << *p << endl;
}
int main()
{
int i = 5;
foo_ptr(&i); // Second foo_ptr gets called; prints 'Second 5'
}
As it might be clear, my question is - If the two definitions of foo in the first case are considered the same then why it is not so for foo_ptr in the second case? Or in other words, why const is ignored in the first case and not so in the second one?
const int* p
is not a constant pointer to an integer, it's a pointer to a constant integer (i.e., [const int] * p rather than const [int * p]). This is why you sometimes see code like:
const int * const p;
which may seem redundant to the uninitiated but is really not - p in that case is a pointer you're not allowed to change, which points to an integer you're also not allowed to change.
Hence the two functions you have in your second case are considered different in terms of the parameters accepted. That's also why you're calling the second function, since i is most definitely not a const integer.
In other words, while const-ing a parameter does not change it in terms of the function signature, that's not what you're doing here. Changing a parameter from "pointer to int" to "pointer to const int" does affect the signature.
The equivalent case to your first code snippet would be providing both of:
void foo_ptr (int * const p)
void foo_ptr (int * p)
During overload resolution, const and volatile specifies on parameters are significant except when they occur at the outermost level of the of the parameter type specification. From the C++ standard, § 13.1.3.4:
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. In particular, for any
type T, “pointer to T,” “pointer to const T,” and “pointer to
volatile T” are considered distinct parameter types, as are “reference
to T,” “reference to const T,” and “reference to volatile T.”
Because when you declare
void foo(const int n)
{
}
All that the const modifier does is prevent n from being modified inside the foo() function. The parameter to this foo() function is still an int. The const modifier does not modify the parameter's type. So both
void foo(int n)
and
void foo(const int n)
are functions that take an int parameter. The only difference between them is that the second one cannot modify it's parameter, while the first one can modify it, like any other non-const variable inside the function.
However, there is a difference between
void foo(const int *p)
and
void foo(int *p)
One is a pointer to a const integer, the other one is a pointer to a mutable integer. They are different types.
Bonus answer:
Both
void foo(int *p)
and
void foo(int * const p)
have the same parameter type. Both functions' parameter is a pointer to an int. Except that the second one's parameter is const value, and the function cannot modify it.
Confused yet?
why const is ignored in the first case and not so in the second one?
In the 1st case, const is qualified for the parameter itself, while in the 2nd case, const is qualified for the pointee, not the pointer itself. Const pointer and pointer to const are not the same thing.
In the 2nd case, pointer to const and pointer to non-const are different and acceptable for overloading. If you make the pointer itself const, i.e. int* const p vs int* p, you'll get the same result as the 1st case.
Related
I want to know what happens if we have a function parameter that is a reference to a const function as shown below.
Version 1
int anotherFunc()
{
std::cout<<"inside anotherFunc"<<std::endl;
return 5;
}
void func(decltype(anotherFunc) const &someFunction)//note the const here
{
std::cout<<"inside func"<<std::endl;
std::cout<<someFunction()<<std::endl;
}
int main()
{
std::cout << "Hello World" << std::endl;
func(anotherFunc);
return 0;
}
Version 2
int anotherFunc()
{
std::cout<<"inside anotherFunc"<<std::endl;
return 5;
}
void func(decltype(anotherFunc) &someFunction)//note the missing const here
{
std::cout<<"inside func"<<std::endl;
std::cout<<someFunction()<<std::endl;
}
int main()
{
std::cout << "Hello World" << std::endl;
func(anotherFunc);
return 0;
}
My questions are:
Are version 1 and version 2 completely equivalent in terms of the function parameter someFunction of the function func? That is adding const for the function paramter someFunction does nothing(i.e. simply ignored).
If const is ignored in these examples then at what point(document) does the C++ standard specify that const will be ignored for this case.
PS: Looking at the generated assembly it does seem that const is ignored for reference to function parameter.
Yes, the const qualifier is ignored when added to an alias for a function type.
From the standard, [dcl.fct]/7:
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored.
[Note 4: A function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. — end note]
[Example 4:
typedef void F();
struct S {
const F f; // OK: equivalent to: void f();
};
— end example]
Are version 1 and version 2 completely equivalent in terms of the function parameter someFunction of the function func?
Yes. There is no such thing as const qualified function type, and as such no thing as reference to const function. If you were to apply const to explicitly written reference to function type, then the program would be ill-formed. But when the const is applied to a type alias or type deduction, then the const is ignored.
does the C++ standard specify that const will be ignored for this case.
Yes.
I have the following functions:
void func(void * const &ptr)
{
std::cerr << "const" << std::endl;
}
void func(void * &&ptr)
{
std::cerr << "mutable" << std::endl;
}
void* const func2()
{
return nullptr;
}
One overload takes const reference parameter and another takes mutable rvalue reference. And there is a function that returns const value.
When I pass that const temporary value to the function:
func(func2());
I expect the const overload to be chosen. But instead I get:
mutable
How is that possible? Why is const return value bound to non-const rvalue reference parameter?
This doesn't however happen when instead of void* I pass const struct to the function:
struct A
{
};
void func(A const &a)
{
std::cerr << "const" << std::endl;
}
void func(A &&a)
{
std::cerr << "mutable" << std::endl;
}
A const func3()
{
return A();
}
int main()
{
func(func3());
return 0;
}
The result is:
const
You can check this on coliru.
What is difference between const void* and const struct?
Is there a way to make overload that takes specifically const values?
Why is const temporary bound to rvalue reference parameter?
Because it's not const by the time overload resolution happens.
[expr.type]
2 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.
A class type prvalue retains its cv-qualifications, but not a void* const prvalue. Overload resolution therefore happens with a plain void* prvalue, which explains the behavior you observe when the rvalue overload is chosen.
The types this paragraph applies to are those "fundamental" types whose value is actually accessed by the program. So a prvalue of such a type is indeed a "pure", ephemeral value, and cannot be modified already.
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
I know this has been asked a lot, but the only answers I could find was when the const-ness was actually casted away using (int*) or similar. Why isn't the const qualifier working on pointer type member variables on const objects when no cast is involved?
#include <iostream>
class bar {
public:
void doit() { std::cout << " bar::doit() non-const\n"; }
void doit() const { std::cout << " bar::doit() const\n"; }
};
class foo {
bar* mybar1;
bar mybar2;
public:
foo() : mybar1(new bar) {}
void doit() const {
std::cout << "foo::doit() const\n";
std::cout << " calling mybar1->doit()\n";
mybar1->doit(); // This calls bar::doit() instead of bar::doit() const
std::cout << " calling mybar2.doit()\n";
mybar2.doit(); // This calls bar::doit() const correctly
}
// ... (proper copying elided for brevity)
};
int main(void)
{
const foo foobar; // NOTE: foobar is const
foobar.doit();
}
The code above yields the following output (tested in gcc 4.5.2 and vc100):
foo::doit() const
calling mybar1->doit()
bar::doit() non-const <-- Why ?
calling mybar2.doit()
bar::doit() const
When a foo instance is const, its data members are const too, but this applies differently for pointers than you might at first think:
struct A {
int *p;
};
A const obj;
The type of obj.p is int * const, not int const *; that is, a constant pointer to int, not a pointer to constant int.
For another way to look at it, let's start with a function:
template<class T>
T const& const_(T const &x) {
return x;
}
Now imagine we have an A instance, and we make it const. You can imagine that as applying const_ on each data member.
A nc;
// nc.p has type int*.
typedef int *T; // T is the type of nc.p.
T const &p_when_nc_is_const = const_(nc.p);
// "T const" is "int * const".
const T &be_wary_of_where_you_place_const = const_(nc.p);
// "const T" is "int * const".
// "const T" is *not* "const int *".
The variable be_wary_of_where_you_place_const shows that "adding const" is not the same as prepending "const" to the literal text of a type.
I am going to answer my own question in this case. Fred Nurk's answer is correct but does not really explain the "why". mybar1 and *mybar1 are different. The first refer to the actual pointer and the latter the object. The pointer is const (as mandated by the const-ness on foo; you can't do mybar1 = 0), but not the pointed to object, as that would require me to declare it const bar* mybar1. The declaration bar* mybar1 is equivalent to bar* const mybar1 when the foo object is const (i.e. pointer is const, not pointed to object).
C++ by default gives a so called bitwise constness - meaning it ensures that no single bit of object has been changed, so it just checks the address of the pointer.
You can read more about it in great book "Effective c++" by S. Meyers