6

Is adding additional const specifiers to function arguments allowed by the standard, like in the following?

foo.h:

int foo(int x, char * data);

foo.c:

// does this match the prototype?
int foo(const int x, char * const data) {
    // this implementation promises not to change x or move data inside the function
}

GCC accepts it with -std=c99 -Wpedantic -Wall -Werror, but that's not necessarily the same as standard-compliant.

This answer shows that the C++ standard allows this - does the C (99) standard allow this too?


There's another question here and a good answer here for C++

Eric
  • 95,302
  • 53
  • 242
  • 374
  • 2
    That's an interesting question, but I don't think it's a good idea to allow it, and if it is, it's bad. Why would you want your definition to not match the declaration? It just makes the code more confusing. – savram Sep 22 '17 at 21:26
  • It is allowed (see "§compatible types", but very bad practice. Such code is likely refused by professionals for being suspectible for other flaws (there is never only one ant). It also might cofuse common tools like Doxygen. So, supporting @savram Why in the world would one want to do this? – too honest for this site Sep 22 '17 at 22:06
  • 7
    Because function prototypes should not contain implementation details, and whether a parameter is `const` is an implementation detail. According to [this answer](https://stackoverflow.com/a/1554820/102441), Herb Sutter's Exceptional C++ actually recommends you do this. – Eric Sep 22 '17 at 22:11
  • 5
    Arguably the real question is not why the language allows you to omit `const` from the declaration, but why it allows you to put it there. It's meaningless noise to consumers of the *declaration* at best. At worst it's a way to make a mistake in declaring an external function. – Alex Celeste Sep 22 '17 at 22:14

3 Answers3

6

This is explicitly allowed by a special case in the rules for function parameter lists. N1570 §6.7.6.3p131 says:

In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.

But you must also understand that the "unqualified version" of a type like const char * is still const char *, because the type const char * is derived from the type const char, and §6.2.5p26 says

A derived type is not qualified by the qualifiers (if any) of the type from which it is derived.

That means that the declaration

void foo (const int x);

is compatible with the definition

void foo (int x) { ... }

but the declaration

void bar (const char *x)

is not compatible with the definition

void foo (char *x) { ... }

You might be wondering why these rules are the way they are. The short version is that in C, all arguments are always passed by copying the value (but not any data pointed to by the value, if there are pointers involved), so it doesn't matter whether an actual argument is const T; the callee receives it as a regular old T regardless. But if you copy a pointer to constant data, the copy still points to constant data, so it does matter and that qualifier should be preserved.


1 Document N1570 is the closest approximation to the 2011 ISO C standard that is publicly available at no charge.

To the best of my knowledge, these rules have not changed significantly since the original 1989 standard. Pre-C89 "K&R" C didn't have prototypes, nor did it have const, so the entire question would be moot.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • I've update the question with some pointers to make it clear that I'm not trying to remove/add qualifiers from a pointer – Eric Sep 22 '17 at 22:16
  • While I was specifically asking about C99, I'll accept this since it's a more generally useful answer - thanks for the comprehensive explanation. – Eric Sep 22 '17 at 22:18
  • 1
    @Eric Yeah, I didn't think you were, but I thought it was important for my answer to make as clear as possible _which_ `const`s can vary between declaration and definition. – zwol Sep 22 '17 at 22:18
  • @Eric To the best of my knowledge these rules have not changed since the original 1989 C standard. – zwol Sep 22 '17 at 22:19
  • 2
    It all makes a lot more sense if you consider that the ignored cv-qualifiers merely affect whether *a local variable* in the function definition is `const` or `volatile`. They have nothing to do with the interface of the function. – R.. GitHub STOP HELPING ICE Sep 22 '17 at 22:38
2

From the C99 spec, 6.7.5.3.15 Function declarators, this is legal:

If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

Eric
  • 95,302
  • 53
  • 242
  • 374
0

cv-qualifiers on the parameter (and not on the type of the parameter) do not affect the type of the parameter, so do not affect the prototype. Things that don't affect the prototype like this can be different between the declaration and definition with no problem. Similarly, the name of the parameter does not affect the prototype, so can also be different between the declaration and definition.

Now what is confusing is that a const appearing here may be on the parameter or may be on the type of the parameter, depending on exactly where it is. If it is part of the type of the parameter, then it does affect the prototype, so must be consistent between the declaration and definition:

int foo(const int x);   // const on the parameter
int foo(int * const x);  // also const on the parater
int foo(const int *x);  // const in the type, not on the parameter
int foo(int const *x);  // also const in the type
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 1
    You're right, but your explanation is bad, because it's making a distinction that doesn't exist in the standard. `const` is a type qualifier, and in all four of your example declarations, its presence changes the type of the parameter `x`. But there's a special case for parameter lists: "each parameter declared with qualified type is taken as having the unqualified version of its declared type". Together with the general rule that the unqualified version of `char *const is `char * but the unqualified version of `const char *` is still `const char *`, this produces the behavior you describe. – zwol Sep 22 '17 at 21:55