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)*)
andint(*)(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-levelconst
const int*
=((const (int))*)
-> not top-level, it's at the second levelconst int* const
=(((const (int))*) const)
-> the secondconst
is at top-levelconst 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 const
s 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/orvolatile
are equivalent. That is, theconst
andvolatile
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 theconst
andvolatile
type-specifiers at the outermost level of the parameter type specification are ignored in this fashion;const
andvolatile
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
Get Private Data Members for Non Intrusive Boost Serialization C++
How to Handle C++ Return Type Std::Vector<Int> in Python Ctypes
How to Embed/Link Binary Data into a Windows Module
Forward Declaration & Circular Dependency
Will Consteval Allow Using Static_Assert on Function Arguments
Inlining Template Specialization
Is It Allowed to Cast Away Const on a Const-Defined Object as Long as It Is Not Actually Modified
Simulating Mouse Clicks on MAC Os X Does Not Work for Some Applications
How to Use the Ansi Escape Code for Outputting Colored Text on Console
Mapping Elements in 2D Upper Triangle and Lower Triangle to Linear Structure
C++: Function Pointer to Functions with Variable Number of Arguments