Почему is_copy_constructible возвращает true для unique_ptr в MSVC12

Я бы ожидал, что это статическое утверждение будет срабатывать:

#include <type_traits>
#include <memory>

int main() {
  static_assert(std::is_copy_constructible<std::unique_ptr<int>>::value, "UPtr has copy constructor?");
}

Но это не так.

Скомпилировано с использованием MSVC12:

Microsoft (R) C/С++ Оптимизация компилятора Версия 18.00.31101 для x64

Ответ 1

static_assert должен срабатывать, std:: unique_ptr имеет неявно удаленный экземпляр копии, поэтому это ошибка. Это выглядит связанным с этим сообщением об ошибке std:: is_copy_constructible не работает:

(1) std:: is_copy_constructible возвращает true для типов с удаленным копировать конструкторы.

(2) std:: is_copy_constructible возвращает true для типов, которые составляют типы, которые не являются копируемыми.

и ответ был:

Спасибо за сообщение об этой ошибке. Мы исправили его, и исправление будет доступно в следующей крупной версии Visual Studio после 2013 года.

Также см. отчет об ошибке: std:: is_copy_constructible не работает правильно.

Обратите внимание, что assert запускает webcompiler, в котором используется обновленная версия Visual Studio. Последнее обновление было на Dec 3, 2015. Утверждение также срабатывает на clang (видеть его в прямом эфире) и gcc.

Я нашел отчет об ошибке: Странное поведение std:: is_copy_constructible, которое имеет очень похожий код для вашего:

static_assert(std::is_copy_constructible<std::unique_ptr<int>>::value, "");

Ответ:

Спасибо за сообщение об этой ошибке. Мы уже исправили это, и исправление доступно в предварительном просмотре VS 2015.

Неясно, какая версия Visual Studio исправлена. Один ответ говорит о версии конца 2013 года, а позже - о предпросмотре 2015 года.

Ответ 2

Вот четыре способа сделать класс несовместимым:

#include <stdio.h>
#include <type_traits>

class A {
public:
    A(const A&) = delete;
    void operator=(const A&) = delete;
};

class B {
private:
    B(const B&) = delete;
    void operator=(const B&) = delete;
};

class C {
public:
    C(const C&) = delete;
    void operator=(const C&) = delete;
    void operator=(C) = delete;
};

class D {
private:
    D(const D&) = delete;
    void operator=(const D&) = delete;
    void operator=(D) = delete;
};

int main() {
    printf("%d %d\n", std::is_copy_constructible<A>::value, std::is_copy_assignable<A>::value);
    printf("%d %d\n", std::is_copy_constructible<B>::value, std::is_copy_assignable<B>::value);
    printf("%d %d\n", std::is_copy_constructible<C>::value, std::is_copy_assignable<C>::value);
    printf("%d %d\n", std::is_copy_constructible<D>::value, std::is_copy_assignable<D>::value);
}

В MSVC2013 x64 (18.00.40629 for x64) он печатает:

1 1    //A
0 1    //B
1 0    //C
0 0    //D

В правильном компиляторе все восемь значений должны быть нулями.

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

P.S. Основная идея для фиксации присвоения взята из этого связанного ответа.