Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it OK for function prototypes and function implementation signatures to use const inconsistently?

I like to declare even value parameters as const where possible, and by searching SO, I found that that's not too uncommon. Like this:

int add(const int a, const int b)
{
    ...
}

But I'm wondering: const for values is an implementation detail of my function, not part of it's interface. So putting it into the prototype seems unnecessary.

This prototype for the above function seems to work just fine:

int add(int a, int b);

Yet I've heard about issues that e.g. declaring the main function's argc as const can lead to problems:

int main(const int argc, const char* const argv[])

So does that mean that int add(int a, int b) and int add(const int a, const int b) are not identical after all?

And if it's technically OK, is that something I should do? I could also leave out variable names in the prototype, but I don't, so maybe I shouldn't leave out const either?

like image 586
futlib Avatar asked Mar 08 '13 15:03

futlib


2 Answers

It's not OK for the function type to differ, but you need to know what is part of the function type and what isn't. In your case, the const for the parameters is not significant, so the function type is the same, although the declaration looks like it differs from the definition.

In your case it's

8.3.5 Functions [dcl.fct]

5 A single name can be used for several different functions in a single scope; this is function overloading (Clause 13). All declarations for a function shall agree exactly in both the return type and the parameter-type-list. The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. 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 ]

As it seems, it need some explanation, so here we go: The important sentence in our case is: After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.

This means that all top-level cv-qualifier are remove. To explain what top-level means, I'll write types in an illegal way to emphasize what a const refers to:

  • const int = (const (int)) -> this is a top-level const
  • const int* = ((const (int))*) -> not top-level, it's at the second level
  • const int* const = (((const (int))*) const) -> the second const is at top-level
  • const int& = ((const (int))&) -> not top-level

I hope this clears some misconceptions about function types up.

For your other questions: I'd advise to keep the declaration and the definition identical, as it might confuse people (like evidenced by this question ;).

For the example of main that you gave:

int main( const int argc, const char* const argv[] )

is, according to the above quote from the standard, equivalent to:

int main( int argc, const char* const* argv )

so the added const for argv does not end up as a top-level const which is removed and it's therefore an ill-formed function type for main, which expects:

int main( int argc, char** argv )

You last question about leaving out the parameter names: I wouldn't do it because to me, they are part of the documentation of the function. They communicate the intent and the semantics of the function (if you choose them wisely).

like image 149
Daniel Frey Avatar answered Sep 29 '22 13:09

Daniel Frey


It should be OK to have differing top level const for function parameters between declarations and definitions of the function, but note that not all compilers are bug free. For example, the Oracle Sun compiler has a long standing issue where it mangles int f(int) and int f(const int) differently.

To avoid any possibility of confusion that you really meant to pass by const reference I usually recommend avoiding top level const in public function declarations and to avoid possible compiler issues I would avoid it in function definitions as well.

(Note that changing char ** to const char* const argv[] in an argument list is not adding top level const, it's a genuine signature change. char** would only be equivalent to char** const in a function parameter list.)

like image 40
CB Bailey Avatar answered Sep 29 '22 11:09

CB Bailey