Consider the following program:
#include <iostream>
#include <utility>
class T {
public:
T() { printf("address at construction: %zx\n", (uintptr_t)this); }
// T(const T&) { printf("copy-constructed\n"); } // helps
// T(T&&) { printf("move-constructed\n"); } // helps
// T(const T&) = default; // does not help
// T(T&&) = default; // does not help
};
T f() { return T(); }
int main() {
T x = f();
printf("address after construction: %zx\n", (uintptr_t)&x);
return 0;
}
Compiling with g++ -std=c++17 test.cpp
gives the following output (same with clang++
):
address at construction: 7ffcc7626857
address after construction: 7ffcc7626887
Based on the C++ reference I would expect the program to output two equal addresses because the copy/move should be guaranteed to be elided (at least in C++17).
If I explicitly define either the copy or the move constructor or both (see commented out lines in the example), the program gives the expected output (even with C++11):
address at construction: 7ffff4be4547
address after construction: 7ffff4be4547
Simply setting the copy/move constructors to default
does not help.
The reference explicitly states
[The copy/move constructors] need not be present or accessible
So what am I missing here?
Because this is a special case where copy elision may not apply.
Quoted from [class.temporary] paragraph 3:
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the non-deleted trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object). [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note ]