I noticed that it's possible to have const
qualifier on a value argument present in the function declaration and then omitted in the definition. That doesn't change the signature of the function. It actually compiles well.
I also noticed that the behavior is different between regular and template classes. Also there's a difference between how it's handled in GCC and Clang.
Consider the following code:
template <typename T> struct A {
void f(const int);
};
template <typename T> void A<T>::f(int x) {
x = 0;
}
struct B {
void f(const int);
};
void B::f(int x) {
x = 0;
}
void f() {
A<float> a;
a.f(0);
B b;
b.f(0);
}
When I compile with GCC I get no errors. With Clang I get:
test.cpp:10:7: error: read-only variable is not assignable
x = 0;
~ ^
test.cpp:26:7: note: in instantiation of member function 'A<float>::f' requested here
a.f(0);
^
GCC took preference of the qualifier at the definition. Clang used the declaration and only for the template class A
.
My questions are:
- Is this regulated by the standard or is this implementation defined?
- Why is the behavior is different between regular and template classes?
- Why is there no error or at least a warning that the
const
qualifier is used inconsistently between the declaration and the definition?
- Are there any situation where this could be useful?
Update:
According to the comments it seems to be a Clang bug. I opened a new ticket.
Update:
The bug is fixed:
Fixed in r203741
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
This is a bug because it prevents legitimate code like:
/* API declaration */
void f(int);
/* Implementation */
void f(const int x) /* my business: x is my local var and I want it const */
{
}
I can't believe anyone would code out of their way to diagnose this as a problem.
Incidentally, GCC, which doesn't complain about this, used to have a warning about this situation. Perhaps it still does:
void f(int func_ptr(void));
void f(int (*func_ptr)(void))
{
}
This is purely a stylistic inconsistency that doesn't serve a purpose, though.