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.
Related
I've read about the various places to put const. Some usages seem clearly useful to me. Others however evade me. It would be really helpful if someone could confirm or correct my understanding as I explain my mental model of things.
These are the same. I'm not sure I understand why this would ever be useful, though. Does it perhaps allow one to initialize const int variables with a function, which in turn allows some compiler optimizations?
const int foo();
int const foo();
These are the same. The returned pointer cannot be used (via dereferencing) to change the values pointed to.
const int * foo();
int const * foo();
This means the returned pointer itself cannot be changed. But, why would it matter if the caller essentially decides to ignore the returned pointer and set it to something else? Is this only really useful if the pointer is returned by reference?
int * const foo();
These are the same. It means you can only pass in const ints, which allows the compiler to optimize things.
int foo(const int foo);
int foo(int const foo);
This means the passed-in pointer cannot be changed. I'm wondering here too, why would it matter unless the pointer is being passed in by reference?
int foo(int * const foo);
This (as a member function) guarantees that the function won't change the state of the object. Also, if the object itself is declared const, then it will only be able to call such functions.
int foo(int foo) const;
const int as return type is pointless, because in an expression the type of a non-class prvalue result of a function call will be stripped of its top-level const anyway. There is no distinction between a const and non-const prvalue of a non-class type. So the type of foo() is just int, no matter whether it is declared to return const int or int.
It would be different if the return type was a const qualified class type, e.g. const std::string foo(). In that case the const would disallow calling non-const-qualified member functions directly on the result of foo(), e.g. foo().resize(42). Still, this is a very rarely used distinction. And as noted in the comments, under certain conditions it can prevent move operations. E.g. in the above if we have a std::vector<std::string> v;, then v.push_back(foo()) will cause a copy, rather than a move, of the returned string into the vector.
However, the const qualifier is part of the return type in the function type and therefore it is technically possible to differentiate a function declared with const return type from one without it. The type of int foo(int foo) is int(int), but the type of const int foo(int foo) is const int(int). (However overloading based on return type is not possible for non-template functions anyway. The return type is not part of the function signature.)
correct
Same as 1. The type of foo() is simply int*.
The top-level const in the function parameter does not affect the type or signature of the function (in contrast to 1. where it does affect the type). So int foo(const int foo); and int foo(int foo); declare the same function and both have type int(int). Top-level const also doesn't affect how a variable can be initialized, so it doesn't make sense to say "you can only pass in const int". There are no const-qualified prvalues of type int anyway and if int foo can be initialized from some expression, then so can const int foo. The const has no implication on initialization or overload resolution. However const can be used like this in a function definition to tell the compiler and yourself that this parameter is not intended to be modified in the definition.
Same as 4.
This is the correct idea, although in the details it is not strictly true. Rather the const is only relevant to overload resolution (behaving as if the implicit object parameter was a const reference) and the type of this (which will be a pointer to const). It is still possible to mutate members declared as mutable or to use const_cast to mutate members. It is also not relevant whether the object itself is const, only whether the glvalue through which the member function is called is.
I'm currently working on material from my data structures class, which involves an exercise where we attempt to define a class mimicking a vector so we can get an understanding of what's going on underneath the hood. Everything made sense until I realized that there are two definitions foroperator[]. In context:
typedef unsigned int size_type;
T& operator[] (size_type i) { return m_data[i]; }
const T& operator[] (size_type i) const { return m_data[i]; }
First question: why is it necessary to have two definitions of operator[]?
Second question: why doesn't this throw a multiple definitions error?
Sorry if anything is vague or seems obvious. I'm new to C++, my only other experience being in Java. Thanks!
This is a common C++ pattern.
The const member function (that is, the one with the keyword const after the prototype) applies when *this is const; in other words, if it is used with a const object. Since *this is const, it's reasonable to assume that it cannot be modified, so operator[] must return a const reference.
The other member function would apply to any object, but since it is less specific than the const one, it only applies if *this is not const. In that case, it is presumably possible to modify the returned reference (object[3] = new_value;), so the return type is not const.
First question: why is it necessary to have two definitions of
operator[]?
Those definitions differ with their const-qualifier (and result type). One of them is const and returns const reference to internal data. It is used if your container is declaread as const, so that reference to it's contents are const as well. If your contianer is not const, non-const operator is used so that internal data can be modified.
Second question: why doesn't this throw a multiple definitions error?
Because const-qualifier is a part of function prototype, which means that void foo(); and void foo() const; are actually two diffrent methods. It allows overloading based on const qualifier of object being used to call the method.
why is it necessary to have two definitions of operator[]?
It's necessary if you want one that you can modify its value (e.g. obj[i] = value) and the other cannot (with const).
why doesn't this throw a multiple definitions error?
Based on $9.3.1/3 states-
"A nonstatic member function may be declared const, volatile, or const volatile. These cvqualifiers affect the type of the this pointer (9.3.2). They also affect the function type (8.3.5) of the member function; a member function declared const is a const member function, a member function declared volatile is a volatile member function and a member function declared const volatile is a const volatile member function."
That said, you can overload a function by using one of those qualifiers, e.g.:
void func();
void func() const;
Each function can be recognized by what you can call signature, when you declare a function in C++ you write the signature of the function, a signature looks like
qualifiers T ( qualifiers U u ) qualifiers;
############ ------------------ ::::::::::
so the signature it's composed of the 2 part that I outlined with # and -, the part with # is the return type and the part - is the description for the arguments accepted by the function.
The point is that if you write the same signature with different qualifiers, C++ still considers this the re-declaration/re-definition of the same function, so qualifiers are part of the signature but when comparing multiple declarations/definitions of functions with the same name they are virtually dropped by your compiler.
You should read more about qualifiers, I simplified the concept here.
You can also add more qualifiers after the signature, the part I outlined with :, to declare some properties about the way your function works, for example const means that you declare that your function doesn't modify the variables that it can access .
This is based on the original question that was asked here.
[Detailed]: Here is the relevant question as requested in comments
Lippman's c++ primer on p.303 mentions:
class Account {
private:
static constexpr int period = 30;
double daily_tbl[period];
}
If the member is used only in contexts where the compiler can substitute the member's value, then an initialized const or constexpr static need not be separately defined. However, if we use the member in a context in which the value cannot be substituted, then there must be a definition for that member.
Also:
For example, if we pass Account::period to a function that takes a const int&, then period must be defined.
So why does passing Account::period to a function that takes a const int&, needs that period must be defined?
It will be very helpful to know,
What is the rationale?
Does the standard explicitly specify these scenarios or these are deduced from a more generic quotation?
If the member never has it's address taken (or, equivalently, a reference bound to it), the compiler can simply use it's value, and this value is the same in every TU, so there's no problem because rvalues don't have to have addresses. Or it can make a copy in each TU or whatever it wants, because you can't observe it's address.
But if you attempt to take the address, the compiler has an obligation to make sure that in all TUs, the address is the same. Because of C++'s truly horrendous TU system, this means needing one, and only one, explicit definition.
I would imagine the rationale to be something along the lines of:
const int* get_addr(const int& i) {
return &i;
}
class Account {
private:
static constexpr int period = 30;
double daily_tbl[period];
const int* period_ptr() {
return get_addr(period);
}
}
Today I discovered that it is possible to declare a function in a header with one signature, and implement it in the source file with different (similar) signature. For example, like this :
// THE HEADER example.hpp
#ifndef EXAMPLE_HPP
#define EXAMPLE_HPP
int foo( const int v );
#endif
// THE SOURCE FILE example.cpp
#include "example.hpp"
int foo( int v ) // missing const
{
return ++v;
}
Is this allowed? Or is this the compiler's extension (I am using g++ 4.3.0) ?
EDIT
I am compiling with pedantic and maximum possible warning level, and I am still not getting a warning or an error.
For the purposes of determining a function signature, any top level const qualifier is ignored. This is because it does not affect function callers. Function parameters are passed by value in any case so the function cannot affect the arguments passed in.
The top level const does affect the body of the function. It determines whether or not the parameter can be changed in the body of the function. It is the same function as the declaration though.
So yes, it is legal and the declaration and definition refer to the same function and not an overload.
Standard reference: 8.3.5 [dcl.fct] / 3: "[...] The type of a function is determined using the following rules. [...] Any cv-qualifier modifying a parameter type is deleted. [...] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. [...]"
Since int is a basic value type, the const modifier does not have any effect here. No matter what you do to your int in the function, this will never be seen by the caller.
You can't do this with int&. In that case, the presence or absence of const is really relevant for the caller, since the int referred to could be modified.
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 :).