Выдвижение функции элемента-члена

код:

#include <iostream>
#include <ios>
#include <string>
#include <type_traits>
#include <memory>

struct value
{
    ~value() = default;
    std::unique_ptr<std::string> s;
};

int main()
{
    std::cout << std::boolalpha;
    std::cout << std::is_move_constructible<value>::value << '\n';
    std::cout << std::is_move_assignable<value>::value    << '\n';

    using str_ptr = std::unique_ptr<std::string>;
    std::cout << std::is_move_constructible<str_ptr>::value << '\n';
    std::cout << std::is_move_assignable<str_ptr>::value    << '\n';

    return 0;
}

Выход (скомпилирован с g++ v4.7.2, http://ideone.com/CkW1tG):

false
false
true
true

Как я и ожидал, value не перемещается конструктивно и не переносится назначаемым, потому что:

~value() = default;

- объявленный пользователем деструктор, который предотвращает неявное генерирование элементов перемещения в соответствии с разделом 12.8 (см. ниже). Если деструктор удален, то value перемещается конструктивно и перемещается назначаемым, как я ожидаю (http://ideone.com/VcR2eq).

Однако, когда определение value изменяется на (http://ideone.com/M8LHEA):

struct value
{
    ~value() = default;
    std::string s;      // std::unique_ptr<> removed
};

вывод:

true
true
true
true

value неожиданно перемещается конструктивно и переносится назначаемым. Я недопонимаю или это ошибка компилятора?


Справочная информация. Я предоставил ответ на этот вопрос и был проинформирован о том, что Tree<> был подвижным, но я не уверен, и я пытаюсь точно определить, или нет.


Раздел 8.4.2 Явно-дефолтные функции стандарта С++ 11 (черновик n3337):

Явно-дефолтные функции и неявно объявленные функции совместно именуемые дефолтными функциями, а реализация должна предоставить им неявные определения (12.1 12.4, 12.8), что может означать определяя их как удаленные. Специальная функция-член предоставляется пользователю, если она объявлена ​​пользователем и явно не дефолт или не удаляется по его первой декларации. Предоставляемая пользователем функция с явно дефолтом (т.е. Явно дефолт после его первого объявления) определяется в точке, где она явно дефолт; если такая функция неявно определяется как удаленная, программа плохо сформирована. [Примечание. Объявление функции по умолчанию после ее первая декларация может обеспечить эффективное выполнение и краткое определение, в то время как позволяя стабильный двоичный интерфейс к развивающейся базе кода.-end note]

Раздел 12.8 Копирование и перемещение объектов класса (пункт 9):

If the definition of a class X does not explicitly declare a move constructor,
one will be implicitly declared as defaulted if and only if
- X does not have a user-declared copy constructor,
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- the move constructor would not be implicitly defined as deleted.

Ответы

Ответ 1

std::is_move_constructible<T> истинно, если if std::is_constructible<T, T&&> истинно, но это не означает, что такая конструкция вызовет конструктор перемещения, только то, что можно построить тип из r значения того же типа. Такая конструкция может использовать конструктор копирования.

Когда value::s является unique_ptr, конструктор копирования типа и оператор назначения копирования определяются как удаленные, так как член s не может быть скопирован. У него нет конструктора перемещения и перемещения оператора присваивания, потому что, как вы указали, у него есть объявленный пользователем деструктор. Это означает, что у него нет конструктора копирования и конструктора перемещения (и никаких других определяемых пользователем конструкторов, которые могли бы принять аргумент типа value&&), поэтому std::is_constructible<value, value&&> является ложным.

Когда value::s является string, конструктор копирования типа и оператор присваивания копии не, определенные как удаленные, так как элемент s является, и поэтому value также можно копировать, а тип CopyConstructible также является MoveConstructible, потому что он действителен в этом контексте:

value v1;
value v2 = std::move(v1);  // calls copy constructor

Это означает, что std::is_constructible<value, value&&> истинно, даже если он вызывает конструктор копирования, а не конструктор перемещения.