Копирование/перемещение elision требует явного определения конструкторов копирования/перемещения

Рассмотрим следующую программу:

#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++ я ожидал, что программа выведет два равных адреса, потому что копия/перемещение должны быть гарантированы (по крайней мере, в C++ 17).

Если я явно определяю либо копию, либо конструктор перемещения, либо оба (см. Пронумерованные строки в примере), программа дает ожидаемый результат (даже с C++ 11):

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

Просто настройка конструктора копирования/перемещения по default не помогает.

Ссылка явно заявляет

[Конструкторы копирования/перемещения] не должны присутствовать или доступны

Так что мне здесь не хватает?

Ответ 1

Потому что это особый случай, когда исключение копирования может не применяться.

Цитируется из пункта 3 [class.temporary]:

Когда объект класса X передается или возвращается из функции, если каждый конструктор копирования, перемещение конструктора и деструктор X является либо тривиальным, либо удаленным, а X имеет хотя бы один не удаленный экземпляр или механизм перемещения, реализации разрешено создавать временный объект для хранения параметра функции или объекта результата. Временный объект создается из аргумента функции или возвращаемого значения соответственно, а параметр функции или возвращаемый объект инициализируется так, как если бы с помощью неиспользуемого тривиального конструктора для копирования временного (даже если этот конструктор недоступен или не будет выбран с помощью разрешения перегрузки, чтобы выполнить копию или перемещение объекта). [Примечание. Эта широта предоставляется, чтобы объекты типа класса были переданы или возвращены из функций в регистрах. - конечная нота]