Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is copy elision valid in default function arguments?

Consider this code:

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';
}

I would expect that for the calls to both default_arg and passed_in, f will just be default-constructed as the copy will be elided*. This would cause both calls to return true. However, neither Clang 3.7 nor GCC 5.3 elide the copy in the default argument of default_arg.

Is copy elision valid in default arguments? Maybe I'm missing something obvious with how default arguments must be evaluated on every call.


Edit: The important part appears to be the presence of a user-declared copy-constructor. If this is present, copy elision occurs. Why should this make a difference?


*Obviously copy elision is currently optional, but I would expect Clang and GCC to do it when possible.

like image 250
TartanLlama Avatar asked Oct 19 '22 15:10

TartanLlama


1 Answers

As T.C. pointed out, copy-elision may not be possible when passing small trivially-copyable types to functions.

This is because the calling conventions for some ABIs (e.g. the System V ABI) assume that sufficiently small trivially-copyable types will be passed in registers rather than through memory. SysV, for example, will categorise such types in the INTEGER argument class, rather than the MEMORY class.

This means that if you pass such an argument to a function which requires taking the address of the parameter, the register contents will need to be copied onto the stack so that there is a valid address. As such, the copy from the rvalue argument to the by-value parameter cannot be carried out, even though the language rules may say its possible.

Of course, copy-elision in such a case is pretty useless for sake of efficiency, but for those curious, a simple way to make copy-elision occur on such platforms is to make the class not trivially-copyable. An example of this is to make the destructor user-provided:

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

This causes copy-elision to occur on both Clang 3.7 and GCC 5.3.

Live Demo

like image 124
TartanLlama Avatar answered Oct 21 '22 05:10

TartanLlama