复制省略在默认函数参数中有效吗?

Is copy elision valid in default function arguments?

考虑这段代码:

struct foo;
foo *p;   

struct foo {
    foo() { p = this; }
};

bool default_arg ( foo f = foo() )
{
    return p == &f;
}

bool passed_in ( foo& f )
{
    return p == &f;
}   

int main()
{
    std::cout << default_arg() << '\n';

    foo f = foo();
    std::cout << passed_in(f) << '\n';
}

我希望对于 default_argpassed_in 的调用,f 将只是默认构造,因为副本将被删除*。这将导致对 return true 的两次调用。但是,Clang 3.7 nor GCC 5.3 都没有在 default_arg.

的默认参数中省略副本

复制省略在默认参数中有效吗?也许我遗漏了一些关于如何在每次调用时评估默认参数的明显信息。


编辑: 重要的部分似乎是存在用户声明的复制构造函数。如果存在,则会发生复制省略。为什么这会有所作为?


*显然复制省略目前是可选的,但我希望 Clang 和 GCC 在可能的情况下这样做。

不幸的是我没有在草案中找到声明(还)但是在 cppreference.com:

Even when copy elision takes place and the copy-/move-constructor is not called, it must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.

我认为这里的问题是,默认的 copy/move 构造函数被优化掉了,因此编译器无法省略副本。但是,如果您实施其中之一,则会发生省略:

#include <iostream>

struct foo;
foo *p;   

struct foo {
    foo() { p = this; std::cout << "default ctor\n"; }

    // define your own copy/move ctor
    // which are doing nothing
    foo(foo const& f) { std::cout << "copy ctor\n"; }
    foo(foo&& f) { std::cout << "move ctor\n"; }

};

bool default_arg ( foo f = foo() )
{
    return p == &f;
}

bool passed_in ( foo& f )
{
    return p == &f;
}   

int main()
{
    std::cout << default_arg() << '\n';

    foo f = foo();
    std::cout << passed_in(f) << '\n';
}

参见 here

C++11 的规则可以在标准的第 12.8(31) 章中找到。 4 种情况下允许复制省略。此处应适用的规则是:

when a temporary class object that has not been bound to a reference would be copied/moved to a class object with the same cv-unqualified type

但是我在一个Note(1.9(11))中找到了这句话:

subexpressions involved in evaluating default arguments are considered to be created in the expression that calls the function, not the expression that defines the default argument

所以当 foo()main 中默认构造时,整个事情应该类似于

bool passed_in_by_value ( foo f )
{
    return p == &f;
}

std::cout << passed_in_by_value(foo()) << '\n';

这也 returns 错误。也许编译器在将其分配给参数时不再将其视为临时值。

不是真正的答案,但可能是一个提示...

正如 T.C. 所指出的,将可简单复制的小类型传递给函数时,复制省略可能是不可能的。

这是因为某些 ABI(例如 System V ABI)的调用约定假定足够小的普通可复制类型将在寄存器中传递,而不是通过内存传递。例如,SysV 将在 INTEGER 参数 class 中对此类类型进行分类,而不是 MEMORY class.

这意味着如果您将这样的参数传递给需要获取参数地址的函数,则需要将寄存器内容复制到堆栈上,以便有一个有效地址。因此,无法执行从右值参数到按值参数的复制,即使语言规则可能说它是可能的。

当然,在这种情况下,为了提高效率,复制省略是毫无用处的,但对于那些好奇的人来说,在此类平台上进行复制省略的一个简单方法是使 class 不平凡可复制。这方面的一个例子是使析构函数由用户提供:

struct foo {
    foo() { p = this; }
    ~foo(){} //user-provided
};  

这会导致在 Clang 3.7 和 GCC 5.3 上发生复制省略。

Live Demo