template <typename TAG>
fn(int left, TAG, int right)
{
}
fn(0, some_type_tag(), 1);
/* or */
fn(0,int(), 1); // where the primitive, int, is not empty.
EDIT: There are two perspectives to this question.
/EDIT
My tags are generally empty-structs, however in some parts of my code they are typedefs of primitive types.So, I'm interested to know if modern compilers will actually pass a parameter. This has two aspects.
Lets keep it to gcc 4.5 and msvc 2008+
Arguments are passed by value; that is, when a function is called, the parameter receives a copy of the argument's value, not its address. This rule applies to all scalar values, structures, and unions passed as arguments. Modifying a parameter does not modify the corresponding argument passed by the function call.
Parameter values to functions are stored on the stack as well, pushed immediately before the return address. Everything what lives on the stack (local variables, parameters etc.)
Arguments in C and C++ language are copied to the program stack at run time, where they are read by the function. These arguments can either be values in their own right, or they can be pointers to areas of memory that contain the data being passed. Passing a pointer is also known as passing a value by reference.
When you call a function in JavaScript, you can pass in any number of arguments, regardless of what the function declaration specifies. There is no function parameter limit. In the above function, if we pass any number of arguments, the result is always the same because it will take the first two parameters only.
C++ has separate translation. Since the parameter can be named in the declaration but not in the function definition and vice versa, there's generally no way whether the compiler knows whether it's safe to omit the function argument. When it's all in the same translation unit, everything could be inlined and the argument name is entirely irrelevant to optimization.
[Added]
The seperate translation may not matter to this specific case, but a compiler builder that would add such an optimization must care. They're not going to put in such optimizations if it breaks perfectly valid code.
As for templates, it's necessary that the type of a template function is equal to the type of a non-template function, else it's impossible to take its address and assign it to a function pointer. Again, you have to take into account seperate translation. Just because you don't take the address of foo<int>
in this TU doesn't mean you won't in another.
It's quite an interesting question actually.
First of all, note that we are in an imperative language, meaning that when you ask for something (even useless, such as constructing an unused object) then the compiler need to comply unless it can come up with an equivalent form. Basically, it could elide the parameter if it could prove that doing so would not change the meaning of the program.
When you write a function call, two things may happen (in the end):
call
is actually emittedIf it is inlined, then no parameter is passed, which effectively means that unused objects can be removed (and not even built) if the compiler can prove that the constructors and destructors involved do not perform any significant work. It works well for tags structures.
When a call is emitted, it is emitted with a specific calling convention. Each compiler has its own set of calling conventions which specify how to pass the various arguments (this
pointer, etc...), generally trying to take advantage of the available registers.
Since only the declaration of the function is used to determine the calling convention (separate compilation model), then it is necessary to actually pass the object...
However, if we are talking about an empty structure, with no method and no state, then this is just some uninitialized memory. It should not cost much, but it does require stack space (at least, reserving it).
Demo using the llvm tryout:
struct tag {};
inline int useless(int i, tag) { return i; }
void use(tag);
int main() {
use(tag());
return useless(0, tag());
}
Gives:
%struct.tag = type <{ i8 }>
define i32 @main() {
entry:
; allocate space on the stack for `tag`
%0 = alloca %struct.tag, align 8 ; <%struct.tag*> [#uses=2]
; get %0 address
%1 = getelementptr inbounds %struct.tag* %0, i64 0, i32 0 ; <i8*> [#uses=1]
; 0 initialize the space used for %0
store i8 0, i8* %1, align 8
; call the use function and pass %0 by value
call void @_Z3use3tag(%struct.tag* byval %0)
ret i32 0
}
declare void @_Z3use3tag(%struct.tag* byval)
Note:
useless
was removed, and no argument is build for ituse
cannot be removed, and therefore space is allocated for the temporary (I hope that the new versions don't 0-initialize the memory)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With