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.
编辑:此问题有两种观点。
/修改
我的标签通常是空结构,但是在我的代码的某些部分,它们是原始类型的typedef。所以,我很想知道现代编译器是否会实际传递参数。这有两个方面。
让我们保持gcc 4.5和msvc 2008 +
答案 0 :(得分:12)
C ++有单独的翻译。由于参数可以在声明中命名但不能在函数定义中命名,反之亦然,因此编译器通常无法知道省略函数参数是否安全。当它全部在同一个翻译单元中时,所有内容都可以内联,参数名称与优化完全无关。
<强> [补充] 强>
单独的转换可能与此特定情况无关,但是添加此类优化的编译器构建器必须关注。如果它破坏了完全有效的代码,它们就不会进行这样的优化。
对于模板,模板函数的类型必须等于非模板函数的类型,否则无法获取其地址并将其分配给函数指针。同样,你必须考虑单独的翻译。仅仅因为你没有在这个TU中取foo<int>
的地址并不意味着你不会在另一个地址。{/ p>
答案 1 :(得分:7)
参数是否被命名对函数签名没有影响,编译器应该将其传入。考虑函数声明中的未命名参数可能在定义中被命名。
现在,在上述模板的特定情况下,编译器可能会内联代码,在这种情况下不会传递任何参数,并且未命名的参数将不起作用。
如果你要做的是标记以解决不同的重载,你总是可以回退到一个指针,这样即使传入它,成本也会很低。
答案 2 :(得分:7)
实际上这是一个非常有趣的问题。
首先,请注意我们使用命令式语言,这意味着当您要求某些内容(甚至无用,例如构造未使用的对象)时,编译器需要遵守,除非它能够提供等效的形式。基本上,如果可以证明这样做不会改变程序的含义,它可能会忽略该参数。
当你编写函数调用时,可能会发生两件事(最后):
call
如果内联,则不传递任何参数,这实际上意味着如果编译器可以证明所涉及的构造函数和析构函数不执行,则可以删除(甚至不构建)未使用的对象任何重要的工作。它适用于标签结构。
发出调用时,将使用特定的调用约定发出调用。每个编译器都有自己的一组调用约定,它们指定如何传递各种参数(this
指针等...),通常试图利用可用的寄存器。
由于只使用函数声明来确定调用约定(单独的编译模型),因此有必要实际传递对象...
但是,如果我们谈论的是一个没有方法且没有状态的空结构,那么这只是一些未初始化的内存。它不应该花费太多,但它确实需要堆栈空间(至少,保留它)。
使用llvm试用版进行演示:
struct tag {};
inline int useless(int i, tag) { return i; }
void use(tag);
int main() {
use(tag());
return useless(0, tag());
}
给出:
%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)
注意:
useless
的调用,并且没有为其构建参数use
无法删除,因此为临时分配了空间(我希望新版本不会初始化内存)答案 3 :(得分:2)
好问题,但你必须尝试编译器。理论上,如果不使用参数,则不必在堆栈中分配参数。但是,调用者必须知道如何调用它,所以我猜测该元素实际上是在堆栈中分配的。