17

I recently had an exchange with another C++ developer about the following use of const:

void Foo(const int bar);

He felt that using const in this way was good practice.

I argued that it does nothing for the caller of the function (since a copy of the argument was going to be passed, there is no additional guarantee of safety with regard to overwrite). In addition, doing this prevents the implementer of Foo from modifying their private copy of the argument. So, it both mandates and advertises an implementation detail.

Not the end of the world, but certainly not something to be recommended as good practice.

I'm curious as to what others think on this issue.

Edit:

OK, I didn't realize that const-ness of the arguments didn't factor into the signature of the function. So, it is possible to mark the arguments as const in the implementation (.cpp), and not in the header (.h) - and the compiler is fine with that. That being the case, I guess the policy should be the same for making local variables const.

One could make the argument that having different looking signatures in the header and source file would confuse others (as it would have confused me). While I try to follow the Principle of Least Astonishment with whatever I write, I guess it's reasonable to expect developers to recognize this as legal and useful.

Scott Smith
  • 3,900
  • 2
  • 31
  • 63
  • 2
    Duplicates: http://stackoverflow.com/questions/117293/use-of-const-for-function-parameters, http://stackoverflow.com/questions/1554750/c-const-keyword-use-liberally, http://stackoverflow.com/questions/1724051/const-correctness-for-value-parameters. Related: http://stackoverflow.com/questions/136880/sell-me-on-using-const-correctness –  Mar 16 '10 at 05:08
  • "I guess the policy should be the same for making local variables const." And I do that, but *only when I think it improves the readability* of the code. – "Programs must be written for people to read, and only incidentally for machines to execute." – Harold Abelson –  Mar 16 '10 at 06:39
  • @Roger, when doesn't the addition of `const` make it easier for someone to read and understand the code? Surely being explicit about the operations that you can perform on a variable always aids readability over being vague... Do you have an example of when you feel that the addition of `const` detracts from the readability of some code? – Len Holgate Mar 16 '10 at 13:27
  • @Len: Mostly short code: `int f(int n) { return g(n)*2; }`. But remember you should strive to keep all functions relatively short, as that also improves readability. A more realistic example: http://bitbucket.org/kniht/scraps/src/tip/cpp/quicksort.hpp (compare the parameters and function body of `void quicksort(Iter const begin, Iter const end, Cmp lt, Distance const len)` to `void quicksort(Iter begin, Iter end, Cmp lt)`). –  Mar 16 '10 at 13:49
  • @Roger, OK I see your point; but I'd probably still put the `const` in... – Len Holgate Mar 16 '10 at 15:16

5 Answers5

30

Remember the if(NULL == p) pattern ?

There are a lot of people who will tell a "you must write code like this":

if(NULL == myPointer) { /* etc. */ }

instead of

if(myPointer == NULL) { /* etc. */ }

The rationale is that the first version will protect the coder from code typos like replacing "==" with "=" (because it is forbidden to assign a value to a constant value).

The following can then be considered an extension of this limited if(NULL == p) pattern:

Why const-ing params can be useful for the coder

No matter the type, "const" is a qualifier that I add to say to the compiler that "I don't expect the value to change, so send me a compiler error message should I lie".

For example, this kind of code will show when the compiler can help me:

void bar_const(const int & param) ;
void bar_non_const(int & param) ;

void foo(const int param)
{
   const int value = getValue() ;

   if(param == 25) { /* Etc. */ } // Ok
   if(value == 25) { /* Etc. */ } // Ok

   if(param = 25) { /* Etc. */ } // COMPILE ERROR
   if(value = 25) { /* Etc. */ } // COMPILE ERROR

   bar_const(param) ;  // Ok
   bar_const(value) ;  // Ok

   bar_non_const(param) ;  // COMPILE ERROR
   bar_non_const(value) ;  // COMPILE ERROR

   // Here, I expect to continue to use "param" and "value" with
   // their original values, so having some random code or error
   // change it would be a runtime error...
}

In those cases, which can happen either by code typo or some mistake in function call, will be caught by the compiler, which is a good thing.

Why it is not important for the user

It happens that:

void foo(const int param) ;

and:

void foo(int param) ;

have the same signature.

This is a good thing, because, if the function implementer decides a parameter is considered const inside the function, the user should not, and does not need to know it.

This explains why my functions declarations to the users omit the const:

void bar(int param, const char * p) ;

to keep the declaration as clear as possible, while my function definition adds it as much as possible:

void bar(const int param, const char * const p)
{
   // etc.
}

to make my code as robust as possible.

Why in the real world, it could break

I was bitten by my pattern, though.

On some broken compiler that will remain anonymous (whose name starts with "Sol" and ends with "aris CC"), the two signatures above can be considered as different (depending on context), and thus, the runtime link will perhaps fail.

As the project was compiled on a Unix platforms too (Linux and Solaris), on those platforms, undefined symbols were left to be resolved at execution, which provoked a runtime error in the middle of the execution of the process.

So, because I had to support the said compiler, I ended polluting even my headers with consted prototypes.

But I still nevertheless consider this pattern of adding const in the function definition a good one.

Note: Sun Microsystems even had the balls to hide their broken mangling with an "it is evil pattern anyway so you should not use it" declaration. see http://docs.oracle.com/cd/E19059-01/stud.9/817-6698/Ch1.Intro.html#71468

One last note

It must be noted that Bjarne Stroustrup seems to be have been opposed to considering void foo(int) the same prototype as void foo(const int):

Not every feature accepted is in my opinion an improvement, though. For example, [...] the rule that void f(T) and void f(const T) denote the same function (proposed by Tom Plum for C compatibility reasons) [have] the dubious distinction of having been voted into C++ “over my dead body”.

Source: Bjarne Stroustrup
Evolving a language in and for the real world: C++ 1991-2006, 5. Language Features: 1991-1998, p21.
http://www.stroustrup.com/hopl-almost-final.pdf

This is amusing to consider Herb Sutter offers the opposite viewpoint:

Guideline: Avoid const pass-by-value parameters in function declarations. Still make the parameter const in the same function's definition if it won't be modified.

Source: Herb Sutter
Exceptional C++, Item 43: Const-Correctness, p177-178.

Community
  • 1
  • 1
paercebal
  • 81,378
  • 38
  • 130
  • 159
  • 1
    I never knew you could change the const-ness of a parameter without resulting in a different function signature. The few times I've felt it useful for a non-reference parameter to be explicitly const, I defined a const reference inside the body of the function. so f(int i_) { const int& i = i_; } Might help if you need to use your anonymous compiler again. – Dennis Zickefoose Mar 16 '10 at 10:16
  • 1
    @Dennis Zickefoose: Your construct is interesting (I thought about a similar case, to hide non const local variable I needed to have initialized by passing them as references to other functions), but it does not remove the original variable (i_ remains accessible), and seems to be it is too much code to add for the produced value... For my anonymous compiler (who wrote Solaris CC?), I just added the "const" to the declaration, too... T_T ... – paercebal Mar 16 '10 at 13:00
  • Aaargh! I got bitten by the same compiler bug ONCE AGAIN! Despite the test script I did write, some functions apparently went undefined (and undetected) in the release build of my .SO, while they were correctly defined in the debug build. Of course, testers got the bug before it could be relase, but anyway... Thanks you, Sun Microsystems, for your broken compiler! – paercebal Jun 15 '10 at 20:53
  • @paercebal: Herb Sutter does not offer the opposite viewpoint with this _guideline_, he just suggests how to best deal with the situation. – agentsmith Apr 02 '20 at 15:37
7

This has been discussed many times, and mostly people end up having to agree to disagree. Personally, I agree that it's pointless, and the standard implicitly agrees -- a top-level const (or volatile) qualifier doesn't form part of the function's signature. In my opinion, wanting to use a top-level qualifier like this indicates (strongly) that the person may pay lip-service to separating interface from implementation, but doesn't really understand the distinction.

One other minor detail: it does apply to references just as well as pointers though...

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 3
    +1 because I mostly agree, but all the standard says is that top-level const as used in the question is an implementation detail of the function, rather than being part of the interface. Saying the standard agrees that it's pointless is reading too much into it. –  Mar 16 '10 at 04:48
  • @Roger: well, yes. Ultimately, the standard is (mostly) restricted to stating requirements, not making comments on style (though if you search for "self-immolation" you'll see that it occasionally ventures beyond that, and in what's officially a normative part of the standard at that! Someday I may have to work through the history to figure out which editor threw in that particular tidbit. :-) – Jerry Coffin Mar 16 '10 at 05:44
  • The most famous programming limerick. :) –  Mar 16 '10 at 05:49
  • "it does apply to references just as well as pointers tho" - not sure how you would want to apply top-level const to references. `T &const` is not valid, and const applied to a reference type typedef is meaningless and ignored always. – Johannes Schaub - litb Mar 16 '10 at 12:38
  • @Johannes: Quite honestly, I'm not sure what I was thinking when I wrote that, but it's obviously nonsense... – Jerry Coffin Mar 16 '10 at 13:43
4

It makes the compiler do part of the work of catching your bugs. If you shouldn't be modifying it, make it const, and if you forget, the compiler will yell at you.

Yuliy
  • 17,381
  • 6
  • 41
  • 47
  • 5
    I agree - except that I sometimes feel that the only "bugs" caught by const flags are those where I forgot to mark something const. –  Mar 16 '10 at 05:03
3

If bar is marked const as above, then the person reading the code, knowing what was passed in, knows at all time exactly what bar contains. There's no need to look at any code beforehand to see if bar got changed at any point along the way. This makes reasoning about the code simpler and thus reduces the opportunity for bugs to creep in.

I vote "good practice" myself. Of course I'm also pretty much a convert to functional languages these days so....


Addressing the comment below, consider this source file:

// test.c++

bool testSomething()
{
    return true;
}

int test1(int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

int test2(const int a)
{
    if (testSomething())
    {
        a += 5;
    }
    return a;
}

In test1 there is no way for me to know what the value being returned will be without reading the (potentially sizable and/or convoluted) body of the function and without tracking down the (potentially distant, sizable, convoluted and/or source-unavailable) body of the function testSomething. Further, the alteration of a may be the result of a horrific typo.

That same typo in test2 results in this at compile-time:

$ g++ test.c++
test.c++: In function ‘int test2(int)’:
test.c++:21: error: assignment of read-only parameter ‘a’

If it was a typo, it's been caught for me. If it isn't a typo, the following is a better choice of coding, IMO:

int test2(const int a)
{
    int b = a;
    if (testSomething())
    {
        b += 5;
    }
    return b;
}

Even a half-baked optimizer will generate identical code as in the test1 case, but you're signalling that care and attention will have to be paid.

Writing code for readability involves a whole lot more than just picking snazzy names.

JUST MY correct OPINION
  • 35,674
  • 17
  • 77
  • 99
  • I would agree if you'd said "if `bar` is a reference or pointer and is marked `const`..." – greyfade Mar 16 '10 at 06:39
  • 2
    Expanded to explain with a non-reference, non-pointer example. – JUST MY correct OPINION Mar 16 '10 at 10:37
  • 1
    Why would you pass something by const value if you plan on copying it into a non-const value before you actually use it? You still can't tell what value will be returned without reading the function [which is what documentation is for anyhow], so the initial const-ness doesn't help you at all. – Dennis Zickefoose Mar 16 '10 at 10:47
  • 1
    Well, the main point of this is protection from brain death. The typo is more of an issue for me than the latter. Copying into a non-const value, however, allows me to do things like, say, involve that parameter in multiple calculations with each calculation knowing for a fact what the initial state is going to be -- no room for error with, say, an inserted line of code accidentally changing a value when it shouldn't be, etc. – JUST MY correct OPINION Mar 16 '10 at 11:36
  • 1
    But you've introduced the opportunity for accidentally using the wrong value. Obviously, if you *need* to keep both the original value around and a modified version, you have to create a second variable. But creating two variables just in case will make your code harder to read, not easier. – Dennis Zickefoose Mar 16 '10 at 11:59
  • 2
    Dennis: at the risk of starting a debate on immutable vs. mutable values, the primary reason to do this is because the original and modified values probably have different semantics associated with them. – Yuliy Mar 17 '10 at 21:19
3

I tend to be a bit of a const fiend so I personally like it. Mostly it's useful to point out to the reader of the code that the variable passed in wont be modified; in the same way that I try to mark every other variable that I create within a function body as const if it's not modified.

I also tend to keep the function signatures matching even though there's not much point in it. Partly it's because it doesn't do any harm and partly it's because Doxygen used to get a bit confused if the signatures were different.

Len Holgate
  • 21,282
  • 4
  • 45
  • 92