Copy/move 省略需要显式定义 copy/move 构造函数

Copy/move elision requires explicit definition of copy/move constructors

考虑以下程序:

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

使用 g++ -std=c++17 test.cpp 编译给出以下输出(与 clang++ 相同):

address at construction:    7ffcc7626857
address after construction: 7ffcc7626887

基于 C++ reference 我希望程序输出两个相等的地址,因为 copy/move 应该保证被省略(至少在 C++17 中)。

如果我显式定义复制或移动构造函数或两者(请参阅示例中注释掉的行),程序会给出预期的输出(即使使用 C++11):

address at construction:    7ffff4be4547
address after construction: 7ffff4be4547

简单地将 copy/move 构造函数设置为 default 没有帮助。

参考文献明确指出

[The copy/move constructors] need not be present or accessible

那么我在这里错过了什么?

因为这是一种特殊情况,复制省略可能不适用。

引自[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 ]