std::unordered_map<T,std::unique_ptr<U>>可复制吗?海湾合作委员会错误?

std::unordered_map<T,std::unique_ptr<U>> copyable? GCC bug?

本文关键字:gt std lt 委员会 错误 ptr map unordered unique 可复制      更新时间:2023-10-16

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 [容器.要求.一般]

我对此的理解是:如果T(在我们的例子中std::pair<const int,std::unique_ptr<int>>(没有CopyInsertableX(在我们的例子中std::unordered_map<int,std::unique_ptr<int>>(,那么X u(a)X u=a的格式不正确。

CopyInsertable

T CopyInsertable X意味着,除了TMoveInsertableX中之外,以下表达式的格式良好:

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

其计算导致以下后置条件成立:v的值保持不变,等效于 *p

我对此的理解是:std::pair<const int,std::unique_ptr<int>>不是CopyInsertable,因为std::unique_ptr<int>是不可复制的:

从此子句 [...] 中指定的unique_ptr模板实例化的类型的每个对象U既不CopyConstructible也不CopyAssignable

§20.8.1 [唯一.PTR]

并且由于 std::pair<const int,std::unique_ptr<int>> 的复制构造函数是默认的:

pair(const pair&) = default;

§20.3.2 [对]

并且由于std::pair<const int,std::unique_ptr<int>>有一个类型 std::unique_ptr<int> 的成员:

template <class T1, class T2> struct pair {

[...]

T2 second;

§20.3.2 [对]

并且由于当不是CopyConstructible类型的所有成员时,默认的复制构造函数将被删除:

X的默认复制/移动构造函数定义为已删除,如果 X 具有:

[...]

  • 类类型M(或其数组(的非静态数据成员,无法复制/移动,因为应用于M相应构造函数的重载分辨率会导致 [...]

§12.8 [类副本]

std::is_copy_constructible

对于可引用的类型T,结果与is_constructible<T,const T&>::value相同, 否则false.

§20.10.4.3 [元.一元.道具]

我对此的理解/解读: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 [元.一元.道具]

我对此的理解是: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/libstdc++错误,还是标准中缺少某些内容?

我目前无法访问 Clang 或 MSVC++,否则我会对它们进行测试。

您的分析中有两个问题。

首先,违反 Require 子句会导致未定义的行为 (§17.6.4.11 [res.on.required](:

违反函数要求中指定的前提条件 段落会导致未定义的行为,除非函数的 抛出:段落指定在违反前提条件时抛出异常。

这意味着,如果您尝试使用非 CopyInsertable 元素复制构造unordered_map,该库可以执行任何它想要的操作。它不一定会导致程序格式不正确(尽管它可能会在复制构造函数实现的深处的某个地方(。

其次,is_constructible性状执行的测试仅限于直接上下文(§20.10.4.3 [meta.unary.prop]/p7,着重号是加的(:

执行访问检查就像在与T和任何无关的上下文中一样 的Args.只有直接上下文的有效性 考虑变量初始化。[注:评价 初始化可能会导致副作用,例如 类模板专用化和函数模板的实例化 专业化,隐式定义函数的生成,以及 等等。这种副作用不在"直接上下文"中,并且可以 导致程序格式不正确。—尾注 ]

换句话说,这基本上只考虑是否存在匹配、可访问且未删除的构造函数签名,而不是实例化构造函数是否会产生格式正确的代码。

该标准必须指定容器的复制构造函数,内容类似于"如果T不能 CopyInsertable to X,则此构造函数不得参与重载解析",以保证 is_copy_constructible 特征的行为方式。标准中没有这样的规范。

正如Marc Glisse在评论中所写的那样,虽然这不是标准强制要求的,但它可以被认为是实现质量问题,因此错误报告是合理的。


编辑:我突然想到,从非CopyInsertable元素的重载解析中删除复制构造函数的要求可能无法实现,因为该属性是根据对allocator_traits<A>::construct(m, p, v)格式正确并具有所需语义的调用指定的。我不相信SFINAE可以确定allocator_traits<A>::construct()呼吁的身体的良好形成。