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()
.