【问题标题】:std::unordered_map<T,std::unique_ptr<U>> copyable? GCC bug?std::unordered_map<T,std::unique_ptr<U>> 可复制?海湾合作委员会错误?
【发布时间】:2014-11-06 08:16:03
【问题描述】:

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 有效,对于包含T 类型的某些容器类型X,其中aX 类型的值:

要求: TCopyInsertableX

§23.2.1 [container.requirements.general]

我对此的理解:如果T(在我们的例子中是std::pair&lt;const int,std::unique_ptr&lt;int&gt;&gt;)不是CopyInsertable变成X(在我们的例子中是std::unordered_map&lt;int,std::unique_ptr&lt;int&gt;&gt;),那么X u(a)X u=a 格式不正确。

开启CopyInsertable

T is CopyInsertable into X 意味着除了TMoveInsertableX 之外,以下表达式是良构的:

allocator_traits&lt;A&gt;::construct(m, p, v)

及其求值导致以下后置条件成立:v 的值不变,等效于*p

我对此的理解:std::pair&lt;const int,std::unique_ptr&lt;int&gt;&gt;不是CopyInsertable,因为std::unique_ptr&lt;int&gt;是不可复制的:

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

§20.8.1 [unique.ptr]

并且由于std::pair&lt;const int,std::unique_ptr&lt;int&gt;&gt;的复制构造函数是默认的:

pair(const pair&amp;) = default;

§20.3.2 [pairs.pair]

由于std::pair&lt;const int,std::unique_ptr&lt;int&gt;&gt; 有一个std::unique_ptr&lt;int&gt; 类型的成员:

template &lt;class T1, class T2&gt; struct pair {

[...]

T2 second;

§20.3.2 [pairs.pair]

而且由于一个类型的所有成员都不是CopyConstructible的情况下,默认的复制构造函数会被删除:

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

[...]

  • 类类型 M(或其数组)的非静态数据成员,因为重载决议应用于 M 的相应构造函数,导致 [...] 函数已删除 [...]

§12.8 [class.copy]

开启std::is_copy_constructible

对于可引用类型T,结果与is_constructible&lt;T,const T&amp;&gt;::value相同, 否则false

§20.10.4.3 [meta.unary.prop]

我对此的理解/阅读: std::is_copy_constructible&lt;std::unordered_map&lt;int,std::unique_ptr&lt;int&gt;&gt;std::is_constructible&lt;std::unordered_map&lt;int,std::unique_ptr&lt;int&gt;,std::unordered_map&lt;int,std::unique_ptr&lt;int&gt; &amp;&gt; 相同。

开启std::is_constructible

给定以下函数原型:

template &lt;class T&gt; add_rvalue_reference_t&lt;T&gt; create() noexcept;

模板特化is_constructible&lt;T, Args...&gt; 的谓词条件当且仅当以下变量定义对于某个发明变量t 的格式正确:

T t(create&lt;Args&gt;()...);

§20.10.4.3 [meta.unary.prop]

我对此的理解: std::is_constructible&lt;std::unordered_map&lt;int,std::unique_ptr&lt;int&gt;&gt;,std::unordered_map&lt;int,std::unique_ptr&lt;int&gt; &amp;&gt; 应该是 std::false_type,而不是 std::true_type,因为 X u(a) 的格式不正确。

我的问题

上面的代码应该被接受吗?这是 GCC/libstdc++ 错误,还是我缺少标准中的某些内容?

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

【问题讨论】:

  • 我认为标准并不能保证这个复制构造函数以可以测试的方式无效。但至少作为 QoI,我认为 libstdc++ PR 会很好。 libc++ (clang) 以同样的方式失败。
  • 容器要求在 Requires 子句中说明。打破 Requires 子句是 UB;它不保证错误,更不用说可以通过类型特征测试的错误了。 is_*_constructible 特征只考虑“变量初始化的直接上下文的有效性”——基本上,匹配 ctor 签名的存在。它不考虑ctor在实例化时是否会编译。
  • 在 MSVS 2013(12.0.30723.00 更新 3)中,即使这样也失败了:static_assert(!std::is_copy_constructible&lt;std::unique_ptr&lt;int&gt;&gt;::value, "Copyable");
  • @AntonSavin 这是他们编译器中的一个已知错误。

标签: c++ gcc g++ language-lawyer c++14


【解决方案1】:

您的分析中有两个问题。

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

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

这意味着如果您尝试使用非 CopyInsertable 元素复制构造 unordered_map,则库可以做任何事情。它不一定会导致程序格式错误(尽管它可能会在复制构造函数实现的深处某处)。

其次,is_constructible trait 执行的测试仅限于直接上下文(§20.10.4.3 [meta.unary.prop]/p7,已添加重点):

访问检查是在与T 和任何无关的上下文中执行的 的Args只有直接上下文的有效性 考虑变量初始化。 [ 注意: 初始化可能会导致副作用,例如 类模板特化和函数模板的实例化 专业化,隐式定义函数的生成,以及 很快。此类副作用不在“直接上下文”中,并且可以 导致程序格式错误。 —尾注 ]

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

标准必须指定容器的复制构造函数,类似于 “如果 T 不是 CopyInsertable 到 X”,则此构造函数不应参与重载解析以保证is_copy_constructible 特征的行为方式符合您的要求。标准中没有这样的规范。

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


编辑:在我看来,从非CopyInsertable 元素的重载解析中删除复制构造函数的要求可能无法实现,因为该属性是根据对allocator_traits&lt;A&gt;::construct(m, p, v) 的调用来指定的,格式正确并具有所需的语义。我不相信 SFINAE 可以确定对 allocator_traits&lt;A&gt;::construct() 的调用正文的格式是否正确。

【讨论】:

    猜你喜欢
    • 2014-03-03
    • 1970-01-01
    • 2015-10-11
    • 2016-06-07
    • 2019-11-07
    • 2015-07-23
    • 2018-02-02
    • 1970-01-01
    • 2020-04-17
    相关资源
    最近更新 更多