55

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?

chanwcom
  • 4,420
  • 8
  • 37
  • 49
  • Also remove the `const` in definition, it is truly useless – Passer By Sep 19 '17 at 05:34
  • 19
  • @StoryTeller How so? – Passer By Sep 19 '17 at 05:44
  • 16
    @PasserBy In the same way that declaring any const local variable isn't always useless. – juanchopanza Sep 19 '17 at 05:45
  • I disagree. `const` is useful when modifying the variable will cause problems elsewhere, which in this case it doesn't – Passer By Sep 19 '17 at 05:52
  • 11
    @PasserBy Modifying the "variable" could cause problems in the implementation of the function, just like modifying any other "variable". To the implementation, the argument is just another local. – juanchopanza Sep 19 '17 at 06:04
  • 13
    `const` is a signal to the compiler that the parameter is not intended to be modified. If the code attempts to modify it, then a warning (or error) will be raised, as this is against the intention of the method/function. – Steve Sep 19 '17 at 06:14
  • 19
    @Steve Far more importantly to my mind, const is a signal to other **Programmers** that the parameter is not intended to be modified. This is far more important then the fact that the compiler may or may not do anything meaningful with it. Write code firstly for other people to read and only incidentally for the compiler to parse, it makes maintenance so much less annoying. – Dan Mills Sep 19 '17 at 09:26
  • 1
    Other concerns you can take into account are external tools as documentation tools which can warn when declaration and definition mismatch. – Jarod42 Sep 19 '17 at 09:55
  • The people who need to read the source file are mostly not the same people who need to read the header. – Alex Celeste Sep 19 '17 at 12:38
  • 1
    The only time it really makes sense is with pointers. –  Sep 19 '17 at 13:12
  • He is 100% dead flat wrong. It isn't meaningless. It affects what the method implementation can do with the formal argument. I wouldn't have put them there in most cases, but I certainly wouldn't be removing them either. Don't let other people create work for you. – user207421 Sep 20 '17 at 03:06
  • 3
    @EJP no, he's not wrong. It has **no effect** on what the implementation can do with the argument. Only the `const` in the definition actually does anything, hence the question. It can even be the other way around: the declaration can put the `const` there and lie about it because, as it has **no effect**, it isn't required to be present in the definition. – Alex Celeste Sep 20 '17 at 10:56
  • @EJP he's not wrong. To give a live example of what Leushenko (adn others) said: see https://ideone.com/IkX6vV - you *can't* use the `const` in declaration to "hint" anything, because you *can't* expect anything just because someone provided `const` in header; it gives no guarantee whatsoever. Only the implementation can define and guarantee const-ness of the arguments in C++. As such, that's the place where people expect it to appear in a meaningful way. –  Sep 20 '17 at 12:22

5 Answers5

80

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • how big of a deal is this? Should I really be making every top level not const? Seems like this would confuse people. – Krupip Sep 19 '17 at 17:33
  • 6
    @snb I'd argue that it's a pretty big deal. If I came across a `const` by-value argument, I would assume that the developer made a mistake or was inexperienced in C++. For arguments, `const` only really makes sense when they are modifiers for references or pointers since they control the mutability of the input. For types passed by-value, it provides no real semantic meaning to add `const` to it. *Edit*: I should add that, however you choose to accept arguments, just _be consistent_. Consistency is important for readability; otherwise `const` arguments may look like they have special meaning. – Human-Compiler Sep 19 '17 at 18:43
  • 2
    @Bitwize How is that an argument that this is "a pretty big deal". Your assumptions about the coding prowess of individuals is pretty irrelevant IMO. Most good ides can generate function templates from your source signature as well, where the const value qualification actually matters, so changing the signature seems quite annoying for not really any benefit. Only recently have things like Clion even checked for this (like the past month or so) – Krupip Sep 19 '17 at 18:58
  • 1
    @snb I'm not trying to argue, I was just stating my opinion. And the `const` qualifications in the implementation still don't make a large amount of difference on literal types either. Since they are literal types, it doesn't stop anyone from simply _copying_ the value and modifying the copy. Since the values are not passed by reference or pointer (and thus are not modifying anything from outside the current directory), the `const`-ness of the argument doesn't actually change what a developer is capable of doing with literal-type arguments. – Human-Compiler Sep 19 '17 at 19:47
  • @snb `const` on a literal-type lacks any semantics for arguments. On the other hand, if you have a `const` reference -- it means that the function will only ever observe (but is unable to directly modify) its input; whereas a non-`const` reference would imply to a developer that the function _may_ modify the input (since it is passed in a mutable context). `const` on literal types don't really make any direct difference otherwise. It doesn't even prohibit what a developer can do in the implementation, since a `const` value can just be copied, and the copy then modified – Human-Compiler Sep 19 '17 at 19:52
  • 4
    @snb - It isn't critical at all. Unless you are the sort that just must have the declaration and definition match. So if you started with `const` everywhere, clients use your API, and then you change you mind... The build system will rebuilt *everything* for no reason unless you bite the bullet and leave the header unchanged. So might as well not have const in the declaration to begin with. – StoryTeller - Unslander Monica Sep 20 '17 at 05:27
  • @Bitwize _"If I came across a `const` by-value argument, I would assume that the developer made a mistake or was inexperienced in C++."_ Do you mean in the header? If not: Good grief. Does `const`-correctness mean nothing to anyone? For some of us, marking everything `const` is not a mistake or sign of sloppy code; it's just the right thing to do, until we need it not to be `const`. Otherwise it's too easy to, _e.g._ modify that var in the middle of the function, breaking an assumption later in the function that it still has what the caller passed in. ('sides, it's just another local variable) – underscore_d Sep 20 '17 at 12:17
  • 1
    @underscore_d I follow `const`-correctness; please don't misunderstand my original message. I am not advocating for writing bad code. I am strictly referring to `const` by-value arguments in a public API. When decorating it in the API, it doesn't _help_ the consumer at all since it has no implied meaning. The consumer shouldn't need to know about internal/implementation development practices. As for in implementation: there are varying opinions for what is proper `const`-correctness -- such as yours, where absolutely everything, even arguments, start `const`; and that's a valid approach. – Human-Compiler Sep 20 '17 at 12:30
  • Bitwize: Thanks for the clarification. I did realise after writing that, that I probably misunderstood. :) So of course, you're completely correct if speaking about the API. As for me, I remember wondering how anyone could ever be bothered to `const`-qualify everything by default... until I started doing it too, haha. – underscore_d Sep 20 '17 at 12:31
43

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.

Artemy Vysotsky
  • 2,694
  • 11
  • 20
  • This also applies to pointer addresses (not pointer values). Use `const char *` in your public declarations, and `const char * const` in your implementation. – Nicholas Shanks Sep 19 '17 at 11:22
  • 5
    It seems that "For **all** types parameter declared const and without const are the same when coming to overload resolution." In fact, they have, as far as the language is concerned, the same *signature*; not only does overload resolution not distinguish it: You cannot even have two functions only differing in top-level param constness defined in the same program. The reason surely is that all parameters (including pointers) in C/C++ are passed by value, so that no function can change them. It is important to understand that `const char *` qualifies the *target*, not the pointer. – Peter - Reinstate Monica Sep 19 '17 at 13:48
  • 1
    For example the TU `struct S {}; void f(const S s){} void f(S s) {}` is illegal, for arbitrarily complex S. – Peter - Reinstate Monica Sep 19 '17 at 13:50
  • Your first paragraph and the quote seem to contradict one another. – Rakete1111 Sep 20 '17 at 05:12
  • @Rakete1111 In what way? – Artemy Vysotsky Sep 20 '17 at 05:19
  • @Artemy You mention primitive types, but the quote doesn't mention that. In fact, it isn't required for the types to be primitive. – Rakete1111 Sep 20 '17 at 05:21
  • I mentioned primitive types just because OP asked about primitive type. I added quote to explicitly confirm comments that same goes for any type. – Artemy Vysotsky Sep 20 '17 at 05:23
  • @Artemy Your first paragraph says that this is only for primitive types, while the quote doesn't mention anything about that. It works for all types. – Rakete1111 Sep 20 '17 at 05:41
  • @Rakete1111 removed - but I feel like I'm just trying to repeat now what StoryTeller has said. – Artemy Vysotsky Sep 20 '17 at 05:51
  • @Artemy Maybe, but you used different words and explained it differently. That can help too if someone doesn't understand the other answers. Also, yours has the quote :) – Rakete1111 Sep 20 '17 at 06:22
15

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

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
7

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.

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
-3

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.

lion
  • 88
  • 5
  • `const` has nothing to do with a function being a ["pure" function](https://en.wikipedia.org/wiki/Pure_function) (no side effects, result depends only on the input). If your compiler failed to hoist `(int)sqrt((double)n))` out of an `i < (int)sqrt((double)n))` loop condition, try turning up the optimization level. Hopefully you weren't writing `i < sqrt(n)`, because that has to do a `double` comparison. – Peter Cordes Sep 20 '17 at 07:23
  • See https://stackoverflow.com/questions/2798188/pure-const-function-attributes-in-different-compilers and https://stackoverflow.com/questions/28405492/how-to-declare-and-define-a-pure-function-in-gcc. – Peter Cordes Sep 20 '17 at 07:25
  • @peter I was probably doing it right to get rid of the warning, didn't think of the `double` comparison, so thanks for the heads up :) Anyway, what I said about `const` was out of the scope of the question, what I meant is that if `const` brings some clarification to whoever reads the code, it should be left, even if it's not required by the compiler; after all, indentation is also not necessary, but it's good practice. – lion Sep 21 '17 at 08:20
  • You're still saying that humans might understand `double foobar(const double)` to mean that `foobar` is a pure function and repeated calls with the same argument can be optimized away (by hand). But that's *not* how most people will interpret that, because that's not what it means in that context. What you're looking for `double foobar(double) PURE;`, where PURE could be an empty macro that still has meaning to humans, or on compilers that support it, something like [`__attribute__((const))`](https://stackoverflow.com/questions/2798188/pure-const-function-attributes-in-different-compilers). – Peter Cordes Sep 21 '17 at 09:23
  • Oh, I think I figured out what you're saying. You mean that you were declaring `const n`, in your own function's definition? Optimizing compilers already figure out when variables don't change on their own. Other commenters or answers (I forget) have already mentioned that using `const` args in the *definition* is generally fine, and can avoid mistakes if you intent to keep a variable unmodified, e.g. by stopping you from passing it by non-const reference. (Definitely useful in C++, because of stuff like `mutate(int &x)` that you don't have in C.) – Peter Cordes Sep 21 '17 at 09:28
  • @peter idk why you keep going about the "pure" function when I said nothing about that. If a parameter is `const`, you know it won't be modified inside the function, that's all. Like how one of `strcpy()`'s parameters is `const`. It may be useless info to some, but it's info. – lion Nov 05 '17 at 17:28
  • strcpy's 2nd arg is a **pointer to** `const`. Totally separate thing from a `const` value (e.g. if it was `const char* const src`: a const pointer-to-const.) I'm going on about pure functions because that's the only way the compiler can know it can optimize away multiple calls to them. But I think I see what you're saying now: you didn't realize that compilers already figure out when a variable doesn't change for optimization purposes whether it's declared `const` or not. – Peter Cordes Nov 05 '17 at 17:47
  • @peter of course the compiler knows when a variable is not changed, otherwise how would it point the error when they do change and shouldn't. I always said that these `const` specifiers are for us, not for the compiler. I know what a pure function is, for all I need, and what is the (relevant, again) effect of `const char *` in `strcpy()` but all this was irrelevant for the question at hand and I feel we have polluted this thread with it. Still, thanks for your intentions to enlighten me. – lion Nov 26 '17 at 19:05