Is it better to remove "const" in front of "primitive" types used as function parameters in the header?

C++HeaderConstantsPrimitive Types

C++ Problem Overview


In the code review process, one of my coworkers mentioned to me that "const"s in front of "primitive types" used as a function parameter in a header is meaningless, and he recommended to remove these "const"s. He suggested using "const" only in the source file in such cases. Primitive types mean types such as "int", "char", "float", etc.

The following is example.

example.h

int ProcessScore(const int score);

example.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

His suggestion is doing as follows:

example.h

int ProcessScore(int score);  // const is removed here.

example.cc

int ProcessScore(const int score) {
  // Do some calculation using score
  return some_value;
}

But I'm somewhat confused. Usually, the user will look at only the header, so if there is inconsistency between the header and the source file, it might cause confusion.

Could anyone give some advice on this?

C++ Solutions


Solution 1 - C++

For all types (not just primitives), the top level const qualifiers in the function declaration are ignored. So the following four all declare the same function:

void foo(int const i, int const j);
void foo(int i, int const j);
void foo(int const i, int j);
void foo(int i, int j);

The const qualifier isn't ignored inside the function body, however. There it can have impact on const correctness. But that is an implementation detail of the function. So the general consensus is this:

  1. Leave the const out of the declaration. It's just clutter, and doesn't affect how clients will call the function.

  2. Leave the const in the definition if you wish for the compiler to catch any accidental modification of the parameter.

Solution 2 - C++

Function parameter declared const and without const is the same when coming to overload resolution. So for example functions

void f(int);
void f(const int);

are the same and could not be defined together. As a result it is better not to use const in declaration for parameters at all to avoid possible duplications. (I'm not talking about const reference or const pointer - since const modifier is not top level.)

Here is exact quote from the standard.

> After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function’s parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. — end note ]

Usefulness of const in the function definition is debatable - reasoning behind it is the same as using const for declaring local variable - it demonstrates to other programmers reading the code the this value is not going to be modified inside the function.

Solution 3 - C++

Follow the recommendations given you in code review.

Using const for value arguments has no semantic value — it is only meaningful (potentially) for implementation of your function — and even in that case I would argue that it is unnecessary.

edit: Just to be clear: your function’s prototype is the public interface to your function. What const does is offer a guarantee that you will not modify references.

int a = 7;
do_something( a );

void do_something(       int& x );  // 'a' may be modified
void do_something( const int& x );  // I will not modify 'a'
void do_something(       int  x );  // no one cares what happens to x

Using const is something akin to TMI — it is unimportant anywhere except inside the function whether or not 'x' is modified.

edit2: I also very much like the information in StoryTeller’s answer

Solution 4 - C++

As many other people have answered, from an API perspective, the following are all equivalent, and are equal for overload-resolution:

void foo( int );
void foo( const int );

But a better question is whether or not this provides any semantic meaning to a consumer of this API, or whether it provides any enforcement of good behaviours from a developer of the implementation.

Without any well-defined developer coding guidelines that expressly define this, const scalar arguments have no readily obvious semantic meaning.

From a consumer: const int does not change your input. It can still be a literal, or it can be from another variable (both const or non-const)

From a developer: const int imposes a restriction on a local copy of a variable (in this case, a function argument). This just means to modify the argument, you take another copy of the variable and modify it instead.

When calling a function that accepts an argument by-value, a copy is made of that argument on the stack for the called function. This gives the function a local copy of the argument for its entire scope that can then be modified, used for calculations, etc -- without affecting the original input passed into the call. Effectively, this provides a local variable argument of its input.

By marking the argument as const, it simply means that this copy cannot be modified; but it does not prohibit the developer from copying it and making modifications to this copy. Since this was a copy from the start, it does not enforce all that much from inside the implementation -- and ultimately doesn't make much difference from the consumer's perspective.

This is in contrast to passing by reference, wherein a reference to int& is semantically different from const int&. The former is capable of mutating its input; the latter is only capable of observing the input (provided the implementation doesn't const_cast the const-ness away -- but lets ignore this possibility); thus, const-ness on references have an implied semantic meaning.

It does not provide much benefit being in the public API; and (imo) introduces unnecessary restrictions into the implementation. As an arbitrary, contrived example -- a simple function like:

void do_n_times( int n )
{
   while( n-- > 0 ) {
       // do something n times
   } 
}

would now have to be written using an unnecessary copy:

void do_n_times( const int n )
{
    auto n_copy = n;
    while( n_copy-- > 0 ) {
        // do something n times
    }
}

Regardless of whether const scalars are used in the public API, one key thing is to be consistent with the design. If the API randomly switches between using const scalar arguments to using non-const scalars, then it can cause confusion as to whether there is meant to be any implied meaning to the consumer.

TL;DR: const scalar types in a public API don't convey semantic meaning unless explicitly defined by your own guidelines for your domain.

Solution 5 - C++

I thought that const is a hint for the compiler that some expressions don't change and to optimize accordingly. For example I was testing if a number is prime by looking for divisors up to the square root of the number and I thought that declaring the argument const would take the sqrt(n) outside of the for loop, but it didn't.

It may not be necessary in the header, but then again, you could say that all you need is to not modify the argument and it is never necessary. I'd rather see const where it is const, not just in the source, but in the header too. Inconsistencies between declaration and definition make me circumspect. Just my opinion.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionchanwcomView Question on Stackoverflow
Solution 1 - C++StoryTeller - Unslander MonicaView Answer on Stackoverflow
Solution 2 - C++Artemy VysotskyView Answer on Stackoverflow
Solution 3 - C++DúthomhasView Answer on Stackoverflow
Solution 4 - C++Human-CompilerView Answer on Stackoverflow
Solution 5 - C++LioView Answer on Stackoverflow