function redefinition: const parameter

827 Views Asked by At

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

  1. Why is #1 a redefinition error but #2 is not?
  2. For the second example, is there a use case for defining two copy constructors (one with const the other without)?
3

There are 3 best solutions below

0
On BEST ANSWER

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.

0
On
  1. #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.
0
On

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.