const pointers in overload resolution - c++

GCC treats these two function declarations as equivalent:
void F(int* a) { }
void F(int* const a) { }
test.cpp: In function 'void F(int*)':
test.cpp:235: error: redefinition of 'void F(int*)'
test.cpp:234: error: 'void F(int*)' previously defined here
This makes some sense because a caller will always ignore the const in this case... it only affects the usage of the parameter 'a' inside of the function.
What I'm wondering is where (if anywhere) the standard says that it's specifically OK to discard qualifiers on pointers used as function arguments for the purpose of overload resolution.
(My real issue is that I'd like to figure out where GCC strips these pointless qualifiers internally, and since the C++ frontend of GCC is littered with comments referencing the standard, the relevant section of the standard might help me find the correct spot.)

Standard says in 8.3.5/3 that for the purposes of determining the function type any cv-qualifiers that directly qualify the parameter type are deleted. I.e. it literally says that a function declared as
void foo(int *const a);
has function type void (int *).
A pedantic person might argue that this is not conclusive enough to claim that the above declaration should match the definition like this one
void foo(int *a)
{
}
or that it should make the code with dual declaration (as in your example) ill-formed, since neither of these concepts are described in the standard in terms of function types.
I mean, we all know that these const were intended to be ignored for all external purposes, but so far I was unable to find the wording in the standard that would conclusively state exactly that. Maybe I missed something.
Actually, in 13.1/3 it has a "Note" that says that function declarations with equivalent parameter declarations (as defined in 8.3.5) declare the same function. But it is just a note, it is non-normative, which suggests that somewhere in the standard there should be some normative text on the same issue.

I think it is basically as prohibited as this:
void foo(int a) {}
void foo(const int a) {}
const on non-references doesn't participate in overloading.
In fact you could even declare
void foo(int a);
and later define
void foo(const int a) {}
where the constness is purely an implementation detail which the caller doesn't care about.

It's the same as:
void foo(int);
void foo(const int);
Being the same to the caller. This is because the function is getting a copy by-value no matter what, so the caller doesn't care if it's thought of as const or not; it makes no difference to it.
It's not legal for the compiler to ignore such things, but there is no difference in overload resolution. The const applies to the implementation of the function.
Illegal would be if the compiler treated:
void foo(int i)
{
i = 5; // good
}
void foo(const int)
{
i = 5; // lolwut?
}
The same, by ignoring the const.

I believe it's the other way around. Any pointer, even nonconst, can be treated like const :).

Related

Is the `const` keyword used in function declaration, or definition or both?

void test(int& in);
void test(const int& in){
}
int main(){
int a = 5;
test(a);
return 0;
}
Above doesn't compile with a link error: undefined reference to `test(int&)'.
I have 3 questions on this:
1- Why do we get a link error? is it because adding const to the definition makes it a completely different function? why would it work when not using references, i.e this works fine:
void test(int in);
void test(const int in){}
..
int a = 5;
test(a);
..
2- Does const go in function declaration, or definition or both? Seems like
the behaviour is different if references are used.
3- Does the const keyword on an argument say "the parameter passed to me should be a constant in the caller" or "this parameter is treated as a constant in this function scope, regardless of it being constant in the caller or not". I'm sure it's the latter but wanted to confirm.
In C++ the two functions:
void test(int& in);
void test(const int& in);
are TWO different, overloaded functions. The first binds to "writeable" integers, the second - to constant ones.
In your code:
int a = 5;
test(a);
a is a modifiable object, hence void test (int &) is a better match from compiler perspective and it selects this function for a call.
The reason why you are getting linker error is that you declared but not defined this function.
In both cases below the const function would have been selected:
int const a = 5;
test(a);
test(10);
Additionally if you only had const version declared as below, it would have been selected as well:
//void test(int &in);
void test(const int &in){}
..
int a = 5;
test(a);
..
As for the second question in case of references - const goes both to declaration and definition as these are different functions.
With normal values there is NO difference between the two when declared:
void test(int in);
void test(const int in);
They refer to THE SAME function. The const version will prevent modification of the function parameter in function definition.
For the third one, when applied to references it means two things:
A reference will be passed to a function and not a copy for an object.
When accompanied by a const it promises to the caller not to modify referenced object and prevents the function from doing so.
A function-definition is always also a function-declaration.
Thus, to differentiate them, the latter is often called a forward-declaration if it is not also a definition.
The function-signature used for overload-resolution derives from the function-declaration, and there are a few peculiarities in deriving it from the written source:
Top-level cv-specifiers on argument-types are discarded.
Top-level cv-specifiers on return-type are discarded if the type is not a class- or union-type. (A struct is a class-type.)
The function is not affected by these rules outside overload-resolution and matching declarations.
Applied to your examples:
void test(int& in);
void test(const int& in) { // const is not top-level
// The above two declarations are for different functions
void test(int in);
void test(const int in){} // const is top-level
// Equivalent declarations above
A function definition is also a function declaration. Thus, you declare two overloaded functions, but only one function is defined, has a body.
The compiler choses the first function, since a is not const and the first choice is the exact match.
You get the linker error, since the first function has no definition, i.e. no body.
It's best to do both, as the const keyword is intended to hint at the user that the variable will not be modified. I believe most compilers will treat a const type as a different type as well, so you'll have a compile error.

Why is const allowed in function declarations?

In C and C++, parameters can be declared const when defining a function:
// without const
void foo(int i)
{
// implementation of foo()
}
// with const
// const communicates the fact that the implementation of foo() does not modify parameter i
void foo(const int i)
{
// implementation of foo()
}
From the caller's perspective, though, both versions are identical (have the same signature). The value of the argument passed to foo() is copied. foo() might change that copy, not the value passed by the caller.
This is all well, but one is allowed to use const also when declaring foo():
void foo(int i); // normal way to declare foo()
void foo(const int i); // unusual way to declare foo() - const here has no meaning
Why is const allowed by the C and C++ standards when declaring foo(), even though it is meaningless in the context of such a declaration? Why allow such a nonsensical use of const?
const is a type qualifier: https://en.cppreference.com/w/cpp/language/cv
As such, they can be added to a type as part of the grammar itself. I suppose they didn't want to specify differently an argument and a variable. (it makes probably the implementation a little bit easier?)
Then the issue is that they don't participate to the signature when they refer to the argument itself (they are part of the signature when they refer for instance to the memory pointed by the argument), as you said in your question.
clang-tidy has a rule to remove the superfluous const: https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
There is no reason to forbid qualifiers on function parameters in non-definition declarations, and allowing it allows source code to keep identical declarations in both the definition and any non-definition declarations.
In a definition, const and other qualifiers have their normal effects, as in:
int foo(const int x)
{
x = 3; // Not permitted.
}
Presuming we do not wish to lose this behavior, permitting qualifiers in definitions while prohibiting them in declarations that are not declarations would unnecessarily complicate the standard. Additionally, it means humans who copied the definition to paste it elsewhere as a non-definition declaration would have to make additional edits, and any software designed to collect external definitions from a source file to create a header file containing non-definition declarations would have to do additional parsing and manipulation to strip qualifiers (but only top-level qualifiers as in int * const p, where the const qualifies p, not those internal to types, as in const int *p, where the const, qualifies the type p points to, not p).
Generally, the C and C++ language do not prohibit “useless” things, such as adding a constant zero (which could arise from a combination of preprocessor macros, where some expression happens to evaluate to zero for certain selections of build options), isolated empty statements, and so on. These things are not of concern unless they contribute to the human propensity to make errors. For example, if adding zero always happened as a result of a mistake (so it never happened as a consequence such as mentioned above) then prohibiting it could have value. However, if there is no value to prohibiting something, then expending effort on prohibiting it is wasteful.
First, const in lis of paramateres of functions isn't meaningless. Consider this code:
void foo(int i)
{
i++;
std::cout << i << "\n";
}
That's not possible if you add const. A parameter passed by value is a local variable for function if it is passed by value. Declaring it const you tell compiler that variable i cannot change its value during its lifetime.
Still not much of use? In C, maybe. In C++ that also means that you cannot call non-const methods of the type-class passed by value. Let's suppose that argument can be an object of a class-type and is an interface to some object that is stored outside of function. Then it does make sense that you declare that your function must leave that object "immutable".
Const doesn't participate in this particular case to form a function signature, so encountering those two variants in same scope or during name look-up leads to an ill-formed program. In other case it may define difference in categories of argument's value.

Why can't void parameters exist in functions with more than one argument?

I "wanted" to use void as a placeholder (or overload disambiguator) or even as a shortcut to have functions with void return type called before entering a specific function like in the following example:
int f(void , int)
{
return 0;
}
void g()
{
}
int main()
{
f(g(), 1);
}
Now, this is not a real world problem (I know that I could just call g() before calling f()) but I was wondering why this is not doable, especially when I can e.g. explicitly return void types i.e. this is legal :
void h()
{
return g(); // this does a return void
}
EDIT
To explain the rationale behind asking this, I first thought that according to C legacy, void would be an incomplete type, so incomplete types cannot appear as function parameters, unlike pointers to incomplete types and hence the void* commonality. Now this would explain void as a "special case" signal for "no parameters" but after C++11 the Standard reads (3.9 [basic.types]) :
A type is a literal type if it is:
void; or
a scalar type; or
....
Being a literal type, I can't find elsewhere any rationale that would exclude void from candidate types for function parameters, neither the equivalent of old C's (prior to C11) "void is not a type". Now, my search may be lacking the required depth which is what I try to compensate for in this Q
A void parameter means the function has no parameters*. It wouldn't make much sense for a function with no parameters to have some parameters.
* This is inherited from C (and presumably kept for compatibility with that language), where a function declared without a parameter list is a function that can take any number of parameters of any type. In C++, such a function would have no parameters, removing the need to use void parameters.
The only real problem here is your function prototype:
int f(void , int)
You cannot give a void as a parameter. You can set it as a return value, meaning "this function returns nothing", or you can give it as only parameter, like this:
int f(void)
It would means "this function takes no parameter", but not as a parameter.
But to give a parameter of void type would mean you could declare a void variable and give it to your function, which would have no sense.
In your sample:
void h()
{
return g(); // this does a return void
}
This does not a return void. This does return nothing. This is as legal as:
void h()
{
return;
}
So here, you can clearly see void is just a meaning of nothing.
Try to use functions returning void as arguments, like you did:
f(g(), 1);
Should be avoided as much as possible.
I've wanted a void argument type in order to have a parameter that is zero-cost in release builds:
#ifdef NDEBUG
typedef DebugTracker* Foo;
#else
typedef void Foo;
#endif
int SomeFunction(Foo foo, ...) {
...
}
I can't find elsewhere any rationale that would exclude void from candidate types for function parameters
#juanchopanza has pointed out one thing, which is that C++ inherited C's f(void) meaning a function that takes no arguments. That being so, C++ still could have the feature but make void parameters act as if they had a default value of nothing...so having such a default if they were at the end of argument lists.
In language-design space, it's always easy to think of the case you have in mind and say "why not?". And if you look at something like libffi then it seems like prohibiting void for arguments makes the system less "pure". There's a count of bytes for each argument, how hard would it be to allow 0?
But there are questions to answer.
If void parameters are possible, then that posits the existence of void variables. How does a void variable act? What's its address? If you can't take the address of a void variable, how does that impact the compiler...the linker...what's going to happen with name-mangling, etc.
I don't know enough to tell you if the pretzel of the existing C and C++ standard can be untwisted in a way that void parameters do more good than harm. It would be an interesting study to take a compiler and some large body of code and think through the details. I upvoted the question as reasonable to ask, but also voted to close as primarily opinion-based, so... that's my 0.02.

Why are argument modifiers (i.e., 'const' or 'volatile') not considered part of a function's type or signature?

Note that the following two functions have the same type and signature:
void foo1(int t) {} // foo1 has type 'void(*)(int)', and signature '(*)(int)'
void foo2(const int t) {} // Also type 'void(*)(int)', signature '(*)(int)'
(the const is not part of the function type or function signature). Similarly, a modifier (const or volatile) on the return type does not influence the function type or function signature.
However, in the function definition itself (not shown), the named variable t does maintain the const qualification in foo2.
There are many StackOverflow questions discussing why the return type of the function is not considered as part of the function signature (used for overload resolution).
However, I cannot find any StackOverflow question that asks why argument modifiers (const or volatile) are not part of the function's type or signature. Also, I have looked directly in the C++11 standards document and find it difficult to unravel.
What is the rationale behind the fact that argument modifiers (i.e., const and volatile) are not part of a function's type or signature?
ADDENDUM For clarity, from R.MartinhoFernandes's answer below, I should clarify that in C++ (I think) the argument modifiers const and volatile are only ignored as part of the function type/signature if they are top-level modifiers - see that answer below.
What is the rationale behind the fact that argument modifiers (i.e., const and volatile) are not part of a function's type or signature?
From a caller's perspective, there is no difference between void foo(int) and void foo(int const). Whatever you pass to it will not be modified, regardless of the modifier: the function will get a copy.
From an implementer's perspective the sole difference is that with void foo(int x) you can mutate x (i.e. your local copy) in the body, but you cannot mutate x with void foo(int const x).
C++ acknowledges these two perspectives. The caller's perspective is acknowledged by making the two declarations void foo(int); and void foo(int const); declare the same function. The implementer's perspective is acknowledged by allowing you to declare a function as void foo(int x); but define it as void foo(int const x) { /*...*/ } if you want to make sure you don't accidentally assign to the argument.
Note that this only applies for top-level const, i.e. const that applies to the whole type. In things like int const& or int const* the modifier only applies to a part of the type, as "pointer to (const (int))", so it is not top-level const. In int *const however, the const again applies to the whole type as in "const (pointer to (int))".

C++ overloading with one parameter const

Why is following not allowed in C++
#include <iostream>
class Sample {
public:
void Method(char x);
void Method(char const x);
};
void Sample::Method(char x) {
char y = x;
}
void Sample::Method(char const x) {
char y = x;
}
int main() {
Sample s;
return 0;
}
Why is following not allowed in C++?
The reason is the very same that the compiler gives you as an compilation error:
Because they are ambiguous!
Why are these methods ambiguous?
Short answer: Because the C++ Standard says so.
What is the rationale behind these overloaded methods being ambiguous?
The compiler does not know whether the caller wants to treat the value of the passed argument as an const or not, there is no way for the compiler to determine that with the information at hand.
Note the emphasis on pass by value here, the argument is being passed by value, and hence the ambiguity. If the argument was passed by reference then the compiler knows for sure how the caller wants to treat the argument because then the actual object itself is being passed, and hence compiler can make a selection of the proper overload.
The following example gives a clearer idea to the explanation above.
Online Sample:
class Sample
{
public:
void Method(char &x){}
void Method(char const x){}
void Method(char const &x){}
};
int main()
{
Sample s;
return 0;
}
It doesn't really answer why, but it is determined by the standard, §1.3.10
The information about a function that participates in overload resolution (13.3): the types of its parameters
and, if the function is a class member, the cv- qualifiers (if any) on the function itself and the class in which the member function is declared.
This just means the cv qualifiers of the arguments are ignored in the overload resolution.
A similar (but not equivalent) example with references works:
class Sample {
public:
void Method(char& x) {}
void Method(const char& x) {}
};
because here the types are different, the first case being a reference to char, the second a reference to const char (as opposed to a const reference to char).
When it comes to function parameters, char and char const are the same data type.
This is still ambiguous. When it's called with a character argument, one version will copy the argument and say "OK, you can change the copy". The other will copy the argument and say "OK, you cannot change the copy." How is the compiler supposed to know whether it can or can't change a copy of something? It could do either just fine.
because it's ambiguous
when you're passing like this
s.Method('x');
what version should you think be called?
The standard says those two declarations are equivalent (13.1.3):
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.
typedef const int cInt;
int f(int);
int f(const int); // redeclaration of f(int)
int f(int) { /* ... */ } // definiton of f(int)
int f(cInt) { /* ... */ } // error: redefiniton of f(int)
http://duramecho.com/ComputerInformation/WhyHowCppConst.html
Because const denotes that variable as having a set value, that cannot be changed after declaration. It is not a different data type.