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.
Related
While writing a custom reflection library I encountered a strange compiler behavior. However I was able to reproduce the problem with a much simplified code. Here is:
#include <iostream>
class OtherBase{};
class Base{};
/* Used only as a test class to verify if the reflection API works properly*/
class Derived : Base, OtherBase
{
public:
void Printer()
{
std::cout << "Derived::Printer() has been called" << std::endl;
}
};
/*Descriptor class that basically incapsulate the address of Derived::Printer method*/
struct ClassDescriptor
{
using type = Derived;
struct FuncDescriptor
{
static constexpr const auto member_address{ &type::Printer };
};
};
int main()
{
Derived derived;
auto address{ &Derived::Printer };
(derived.*address)(); // -> OK it compiles fine using the local variable address
(derived.*ClassDescriptor::FuncDescriptor::member_address)(); // -> BROKEN using the address from the descriptor class cause fatal error C1001 !
}
While trying debugging this problem I noticed that:
It happen only if Derived has multiple inheritance.
If I swap static constexpr const auto member_address{ &type::Printer } with inline static const auto member_address{ &type::Printer } it works.
Is it just a compiler bug, or I'm doing something wrong ?
Can I solve this problem while keeping the constexpr ?
Please note that I'm using MSVC 2017 with the compiler version 19.16.27024.1
All compiler options are default except for /std:c++17 enabled.
I know that updating (and surely i'll do it) the compiler version to the last one will probably solve the issue, but for now I would like to understand more about this problem.
About C1001, Microsoft Developer Network suggests that you remove some optimizations in your code: Fatal Error C1001. Once you've worked out which optimization is causing the issue, you can use a #pragma to disable that optimization in just that area:
// Disable the optimization
#pragma optimize( "", off )
...
// Re-enable any previous optimization
#pragma optimize( "", on )
Also, a fix for this issue has been released by Microsoft. You could install the most recent release.
const and constexpr:
const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.
constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.
When applied to functions the basic difference is this:
const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members.
constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly:
The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.)
As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed.
The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)
When can I / should I use both, const and constexpr together?
A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.
constexpr const int N = 5;
is the same as
constexpr int N = 5;
However, note that there may be situations when the keywords each refer to different parts of the declaration:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
}
Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).
B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as
constexpr void f();
needs to be declared as
constexpr void f() const;
under C++14 in order to still be usable as a const function.
You could refer to this link for more details.
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.
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);
}
}
In C++, I tend to omit the parameter's name under some circumstances. But in C, I got an error when I omitted the parameter's name.
Here is the code:
void foo(int); //forward-decl, it's OK to omit the parameter's name, in both C++ and C
int main()
{
foo(0);
return 0;
}
void foo(int) //definition in C, it cannot compile with gcc
{
printf("in foo\n");
}
void foo(int) //definition in C++, it can compile with g++
{
cout << "in foo" << endl;
}
Why is that? Can't I omit the parameter's name in C function definition?
No, in C you cannot omit identifiers for parameters in function definitions.
The C99 standard says:
[6.9.1.5] If the declarator includes a parameter type list, the
declaration of each parameter shall include an identifier, except for
the special case of a parameter list consisting of a single parameter
of type void, in which case there shall not be an identifier. No
declaration list shall follow.
The C++14 standard says:
[8.3.5.11] An identifier can optionally be provided as a parameter
name; if present in a function definition , it names a parameter
(sometimes called “formal argument”). [Note: In particular, parameter
names are also optional in function definitions and names used for a
parameter in different declarations and the definition of a function
need not be the same.]
The reason is that that's what the respective language standards say, but there is a rationale for the difference.
If you don't provide a name for a parameter, then the function cannot refer to that parameter.
In C, if a function ignores one of its parameters, it usually makes sense just to remove it from the declaration and the definition, and not pass it in any calls. An exception might be a callback function, where a collection of functions all have to be of the same type but not all of them necessarily use their parameters. But that's not a very common scenario.
In C++, if the function is derived from a function defined in some parent class, it has to have the same signature as the parent, even if the child function has no use for one of the parameter values.
(Note that this is not related to default parameters; if a parameter in C++ has a default value, the caller doesn't have to pass it explicitly, but the function definition still has to provide a name if it's going to refer to it.)
UPDATE: It's likely that the next edition of the C standard (C23, ISO/IEC 9899:2023 (E)) will allow parameter names to be omitted.
On a purely practical level, I have deal with this daily. The best solution to date is to use the pre-processor. My common header file contains:
//-------------------------------------------------------------------------
// Suppress nuisance compiler warnings. Yes, each compiler can already
// do this, each differently! VC9 has its UNREFERENCED_PARAMETER(),
// which is almost the same as the SUPPRESS_UNUSED_WARNING() below.
//
// We append _UNUSED to the variable name, because the dumb gcc compiler
// doesn't bother to tell you if you erroneously _use_ something flagged
// with __attribute__((unused)). So we are forced to *mangle* the name.
//-------------------------------------------------------------------------
#if defined(__cplusplus)
#define UNUSED(x) // = nothing
#elif defined(__GNUC__)
#define UNUSED(x) x##_UNUSED __attribute__((unused))
#else
#define UNUSED(x) x##_UNUSED
#endif
An example of the use of UNUSED is:
void foo(int UNUSED(bar)) {}
Sometimes you actually need to refer to the parameter, for example in an assert() or debug statement. You can do so via:
#define USED_UNUSED(x) x##_UNUSED // for assert(), debug, etc
Also, the following are useful:
#define UNUSED_FUNCTION(x) inline static x##_UNUSED // "inline" for GCC warning
#define SUPPRESS_UNUSED_WARNING(x) (void)(x) // cf. MSVC UNREFERENCED_PARAMETER
Examples:
UNUSED_FUNCTION(int myFunction)(int myArg) { ...etc... }
and:
void foo(int bar) {
#ifdef XXX
// ... (some code using bar)
#else
SUPPRESS_UNUSED_WARNING(bar);
#endif
}
You can omit the parameter name in the function prototype, but you must declare it in the function implementation. For example, this compiles and runs just fine under GCC 4.6.1
void foo(int, int);
void foo(int value, int secondValue)
{
printf("In foo with value %d and %d!\n", value, secondValue);
}
int main(int argc, char **argv)
{
foo(10, 15);
return 0;
}
Outputs: In foo with value 10 and 15!
As to why (other than because the standards say so): C++ allows you to call a function without using all of the arguments, while C doesn't. If you don't supply all the arguments to a function in C, then the compiler will throw error: too few arguments to function 'foo'
code snippet:
// some code
SDL_Surface* t = Display->render_text(text);
int z=100;
Display->blit_image(t,z,100);
// some more code
does not compile because z magically changes to an int&,
file.cpp:48: error: no matching function for call to ‘display::blit_image(SDL_Surface*&, int&, int)
how can this happen?
post scriptum:
the following works if i put them in place of Display->blit_image(t,z,100)
Display->blit_image(t,z,100,0);
but i am sure that th 4th param is optional because the exact same function works elsewhere without it
pps: i created a minimal-case of my code that behaves as describd above
it's 3 files:
monkeycard.cpp: http://pastebin.com/pqVg2yDi
display.hpp: http://pastebin.com/xPKgWWbW
display.cpp: http://pastebin.com/nEfFX1wj
g++ -c display.cpp monkeycard.cpp fails with:
monkeycard.cpp: In member function ‘void monkeycard::messagebox(std::string)’:
monkeycard.cpp:28: error: no matching function for call to ‘display::blit_image(SDL_Surface*&, int&, int)’
display.hpp:26: note: candidates are: void display::blit_image(SDL_Surface*, int, int, SDL_Rect*)
The error message tells you what you're trying to pass. With automatic conversions and whatnot, that doesn't mean the function must have exactly that signature.
int& here just means that the parameter you've provided is an lvalue, and so it could be passed as a non-const reference. A function can match with that parameter as an int&, const int&, int, long, const float&, etc.
the point is that if instead of z i
write 100 it works.
That's interesting. I can't immediately think of a way to write a function that accepts an integer literal, but not an integer variable. The following code compiles, of course:
struct SDL_Surface;
struct SDL_Rect;
struct display {
void foo(SDL_Surface* img, int x=0, int y=0, SDL_Rect* clip=0) {}
};
int main() {
display d;
int z = 0;
SDL_Surface *p = 0;
d.foo(p,z,100);
}
So there must be something else you haven't mentioned yet, which causes the issue.
Edit: visitor and Charles Bailey (in a comment) have the answer. The defaults are missing from your declaration of the function, so as far as the compiler is concerned you are trying to call a 4-parameter function with 3 arguments. The & is not the problem.
For future reference: when James McNellis asked you for "the" declaration of your function, he meant the declaration which is visible in the translation unit making the call. In your pastebin code, the definition is not visible in that translation unit, and the compiler cannot reach in to a completely different .cpp file and realise that the function is supposed to have parameter defaults. In C++, default values are set up in the calling code, for reasons to do with how calling conventions work.
Having seen the code, the defaults should be given in the header and not in the implementation file.
When you are compiling "monkeycard.cpp", the compiler has only the information in the headers to work with. The compiler has no idea that blit_image has default arguments, and therefore cannot match the function to call.
I suspect that the function isn't declared properly.
There needs to be a prototype inside the class display scope such as:
void blit_image(SDL_Surface* img, int x=0, int y=0, SDL_Rect* clip=NULL);
When you pass an int & parameter (such as any named variable of type int) to an int argument, the value of the int & is copied into the new object of type int. The difference in types implies a conversion which entails a copy which implements pass-by-value. That is just how the C++ formalism works.
The error message you see is nothing else than a specific convention, which that particular compiler uses to generate error messages in cases like that. Apparently, when the compiler is unable to resolve a function call, it generates an error message where every Lvalue argument is reported as having reference type and every Rvalue argument is reported as having non-reference type. This makes some sense, since references in C++ exist specifically for implementing the concept of run-time-bound Lvalue. In fact, it might even turn out that this is exactly how the overload resolution is implemented internally in that compiler.
As for the reason for the error: the function you are trying to call does not exist (or exists, but has a non-matching set of parameters).
P.S. You said in the comments that the matching function actually does exist. That would mean that there's either a problem with the visibility of the function declaration, or a problem with the code you posted being "fake" (i.e. it is not the code you were actually compiling).
Primitives are not reference types in C++.
How do you know that it's the int& that's the cause for the error? The error simply says that the signature is in error. I'd recommend going back and checking the method signature to see what the root cause is.