简体   繁体   中英

Is an unnamed parameter actually passed during a function call?

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.

  1. Function declaration vs definition. The declaration might not name the parameter, but the declaration might do.This isn't the perspective of interest.
  2. The template perspect, spectifically in meta-programming. The parameter In question is a tag used to pull out a meta-structure out of a trait. This is why the parameter is unnamed, I only care about the compile-time information - the type of the tag.

/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.

  1. Sizing the stack, taking into account the size of the unnamed parameter type.
  2. Actually constructing the stack with the passed value.

Lets keep it to gcc 4.5 and msvc 2008+

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.

Whether the parameter is named or not has no effect on the function signature, and the compiler should pass it in. Consider that an unnamed parameter in the declaration of a function might be named in the definition.

Now, in the particular case of templates like the one above, chances are that the compiler will inline the code, in which case no arguments will be passed, and the unnamed argument will have no effect.

If what you are trying to do is tagging to resolve to different overloads, you can always fall back to a pointer, so that even if it is passed in, the cost will be minimal.

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):

  • either it is inlined
  • or a call is actually emitted

If 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:

  • how the call to useless was removed, and no argument is build for it
  • how to call to use cannot be removed, and therefore space is allocated for the temporary (I hope that the new versions don't 0-initialize the memory)

Good question, but you'll have to try on your compiler. In theory, if a parameter is not used, it doesn't have to be allocated in the stack. However, callers have to know how to call it, so my guess is that the element gets actually allocated in the stack.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM