Inconsistent Use of Const Qualifier Between Declaration and Definition

Inconsistent use of const qualifier between declaration and definition

This behavior is defined by the standard and as far as I can tell gcc is correct here, if we look at the draft C++ standard section 13.1 Overloadable declarations paragraph 3 says:

[...]-Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called.

and provides this example:

[ Example:
typedef const int cInt;

int f (int);
int f (const int); // redeclaration of f(int)
int f (int) { /* ... */ } // definition of f(int)
int f (cInt) { /* ... */ } // error: redefinition of f(int)
—end example ]

and some details clarifying that that this applies only to the outermost cv qualifiers (emphasis mine):

Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.123 In particular, for any type T, “pointer to T,” “pointer to const T,” and “pointer to volatile T” are considered distinct parameter types, as are “reference to T,” “reference to const T,” and “reference to
volatile T.”

and as far as I can tell this applies to template functions in template classes as well from section 14.8 Function template specializations specifically 14.8.3 Overload resolution which says:

[...]The complete set of candidate functions includes all the synthesized declarations and all of the non-template overloaded functions of the same name. The synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 13.3.3.144

const qualifier accepted in ctor declaration/definition (llvm bug?)

As far as I can tell this does not look like valid syntax, the draft C++ standard section 12.1 Constructors paragraph 1 says:

Constructors do not have names. A special declarator syntax is used to declare or define the constructor.
The syntax uses:

— an optional decl-specifier-seq in which each decl-specifier is either a function-specifier or constexpr,


— the constructor’s class name, and


— a parameter list

and we can see from section 7.1.2 Function specifiers are as follows:

function-specifier:
inline
virtual
explicit

Via Ali in the comment above a bug report was filed for this it was confirmed and resolved.

Function parameters: const matching declaration and definition

When you pass by value, the argument is effectively a local variable of the function. Whatever you pass is copied. If the argument is a const T, it just means the function itself cannot modify its own variable. The caller shouldn't know or care about that.

Passing by const T& actually refers to access of a variable which does not belong to the function. Same with const T*. But not the same with T* const, that one would just mean the function itself cannot modify its own pointer. The pointer belongs to the function, if the function wants to reassign it to point to something else that's its own business. What it points to does not belong to the function, so whether the function gets const access or not is very relevant to the caller.

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

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).

Why doesn't the Standard allow the definition of the two functions `f` below?

Top-level consts on the parameter types are not part of the function signature. So the two versions of f() you've defined are the same function as far as overload resolution is concerned making the second one a redefinition.

From §13.1/3 [over.load]

— Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called. [ Example:

 typedef const int cInt;
int f (int);
int f (const int); // redeclaration of f(int)
int f (int) { /* ... */ } // definition of f(int)
int f (cInt) { /* ... */ } // error: redefinition of f(int)

—end example ]

Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations.

What is the meaning of 14.8.2 paragraphs 3 and 4 in the C++ Standard?

Consider:

template <class T> void f(T t) { t = 5; }

f<int> is well-formed, but f<const int> is not, because it attempts to assign to a const variable.

See: Use of 'const' for function parameters

Two functions declarations share one definition, is this legal?

The inner foo is just another forward deceleration of the same foo(). Consider the following example:

 int foo();
int foo();

int main() {
cout << foo() << endl;
}

int foo() { // one definition
return 42;
}

This will compile and run and there is no ambiguity because the compiler will replace the use of the same function with the same code.

It is fine to re declare functions.

Isn't const redundant when passing by value?

The const qualifier prevents code inside the function from modifying the parameter itself. When a function is larger than trivial size, such an assurance helps you to quickly read and understand a function. If you know that the value of side won't change, then you don't have to worry about keeping track of its value over time as you read. Under some circumstances, this might even help the compiler generate better code.

A non-trivial number of people do this as a matter of course, considering it generally good style.



Related Topics



Leave a reply



Submit