Std:: unordered_map <T, std:: unique_ptr <U>> можно скопировать? Ошибка GCC?

g++ --version дает:

g++.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Программа:

#include <memory>
#include <type_traits>
#include <unordered_map>

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

int main () {   }

Результат компиляции:

.\unorderedmapcopyable.cpp:5:1: error: static assertion failed: Copyable
 static_assert(!std::is_copy_constructible<std::unordered_map<int,std::unique_ptr<int>>>::value,"Copyable");
 ^

Соответствующий стандарт:

В контейнерах, которые можно копировать

Для утверждений X u(a) и X u=a для допустимого значения для некоторого типа контейнера X, который содержит тип T, где a - значение типа X:

Требуется: T есть CopyInsertable в X

§23.2.1 [container.requirements.general]

Мое понимание этого: Если T (в нашем случае std::pair<const int,std::unique_ptr<int>>) не CopyInsertable в X (в нашем случае std::unordered_map<int,std::unique_ptr<int>>), то X u(a) и X u=a не были хорошо сформированы.

Вкл CopyInsertable

T CopyInsertable в X означает, что в дополнение к T, являющемуся MoveInsertable, в X, следующее выражение хорошо сформировано:

allocator_traits<A>::construct(m, p, v)

и его оценка приводит к следующему постусловию: Значение v не изменяется и эквивалентно *p.

Мое понимание этого: std::pair<const int,std::unique_ptr<int>> не CopyInsertable, из-за того, что std::unique_ptr<int> не копируется:

Каждый объект типа U, созданный из шаблона unique_ptr, указанного в этом подпункте [...], не является CopyConstructible и CopyAssignable.

§20.8.1 [unique.ptr]

И из-за того, что конструктор копирования std::pair<const int,std::unique_ptr<int>> по умолчанию:

pair(const pair&) = default;

§20.3.2 [pairs.pair]

И из-за того, что std::pair<const int,std::unique_ptr<int>> имеет член типа std::unique_ptr<int>:

template <class T1, class T2> struct pair {

[...]

T2 second;

§20.3.2 [pairs.pair]

И из-за того, что дефолтные конструкторы копий удаляются, когда это не так, что все члены типа CopyConstructible:

По умолчанию конструктор copy/move для класса X определяется как удаленный, если X имеет:

[...]

  • нестатический член данных типа класса M (или его массив), который нельзя скопировать/перемещать, поскольку разрешение перегрузки, применяемое к соответствующему конструктору M, приводит к [...] функции, которая удаляется [...]

§12.8 [class.copy]

Вкл std::is_copy_constructible

Для ссылочного типа T, тот же результат, что и is_constructible<T,const T&>::value, иначе false.

§20.10.4.3 [meta.unary.prop]

Мое понимание/чтение этого: std::is_copy_constructible<std::unordered_map<int,std::unique_ptr<int>> совпадает с std::is_constructible<std::unordered_map<int,std::unique_ptr<int>,std::unordered_map<int,std::unique_ptr<int> &>.

Вкл. std::is_constructible

Учитывая следующий прототип функции:

template <class T> add_rvalue_reference_t<T> create() noexcept;

условие предиката для специализированной специализации is_constructible<T, Args...> должно выполняться тогда и только тогда, когда следующее определение переменной будет хорошо сформировано для некоторой изобретенной переменной T:

T t(create<Args>()...);

§20.10.4.3 [meta.unary.prop]

Мое понимание этого: std::is_constructible<std::unordered_map<int,std::unique_ptr<int>>,std::unordered_map<int,std::unique_ptr<int> &> должно быть std::false_type, а не std::true_type, так как X u(a) не является корректным.

Мой вопрос

Должен ли быть принят этот код? Является ли это ошибкой GCC/libstdС++, или есть что-то в стандарте, который мне не хватает?

В настоящее время у меня нет доступа к Clang или MSVС++, иначе я бы тестировал их.

Ответ 1

В вашем анализе есть две проблемы.

Во-первых, нарушение предложения Requires приводит к поведению undefined (§17.6.4.11 [res.on.required]):

Нарушение предусловий, указанных в функциях. Требуется: абзац приводит к поведению undefined, если только функции Выбрасывает: абзац указывает на исключение при условии, что предварительное условие нарушено.

Это означает, что библиотека может делать все, что захочет, если вы попытаетесь скопировать конструкцию unordered_map с элементом non-CopyInsertable. Это не обязательно приводит к плохому формированию программы (хотя, вероятно, это будет где-то глубоко внутри реализации конструктора копирования).

Во-вторых, тестирование, выполненное по признаку is_constructible, ограничено непосредственным контекстом (§20.10.4.3 [meta.unary.prop]/p7, добавлено выделение):

Проверка доступа выполняется так, как если бы в контексте, не связанном с T, и любой из Args. Только действительность непосредственного контекста рассматривается переменная инициализация. [Примечание: оценка инициализация может привести к побочным эффектам, таким как создание шаблонов шаблонов шаблонов и шаблонов функций специализация, генерация неявно определенных функций и скоро. Такие побочные эффекты не находятся в "непосредственном контексте" и могут приводят к плохой форме программы. -end note]

Другими словами, это в основном просто рассматривает, есть ли соответствующая, доступная и не удаленная подпись конструктора, а не при создании экземпляра конструктора в результате корректного кода.

Стандарт должен был бы указывать конструктор копирования контейнеров с чем-то по строкам "этот конструктор не должен участвовать в разрешении перегрузки, если T не является КопироватьInsertable в X", чтобы гарантировать, что признак is_copy_constructible ведет себя как вы этого хотите. В стандарте такой спецификации нет.

Как писал Марк Комментарии в комментариях, хотя это не предусмотрено стандартом, его можно рассматривать как проблему качества реализации, поэтому отчет об ошибке будет разумным.


Изменить: мне пришло в голову, что требование удалить конструктор копирования из разрешения перегрузки для элементов не CopyInsertable, вероятно, не реализуется, так как это свойство указано в терминах вызова allocator_traits<A>::construct(m, p, v), который хорошо сформирован и имеющий требуемую семантику. Я не считаю, что SFINAE может определить правильную форму тела вызова allocator_traits<A>::construct().