std::unordered_map<T,std::unique_ptr<U>&gt;可复制?海湾合作委员会错误? [英] std::unordered_map&lt;T,std::unique_ptr&lt;U&gt;&gt; copyable? GCC bug?

查看:20
本文介绍了std::unordered_map<T,std::unique_ptr<U>&gt;可复制?海湾合作委员会错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

g++ --version 产生:

g++.exe (x86_64-posix-seh-rev0, 由 MinGW-W64 项目构建) 4.9.1版权所有 (C) 2014 Free Software Foundation, Inc.这是免费软件;查看复制条件的来源.没有保修单;甚至不考虑适销性或特定用途的适用性.

程序:

#include <memory>#include <type_traits>#include <unordered_map>static_assert(!std::is_copy_constructible<std::unordered_map<int,std::unique_ptr<int>>>::value,"可复制");int main () { }

编译结果:

.unorderedmapcopyable.cpp:5:1:错误:静态断言失败:可复制static_assert(!std::is_copy_constructible<std::unordered_map<int,std::unique_ptr<int>>>::value,"可复制");^

相关标准:

关于可复制的容器

对于语句 X u(a)X u=a 有效,对于某些容器类型 X,其中包含类型T,其中 aX 类型的值:

<块引用>

要求: TCopyInsertableX

§23.2.1 [container.requirements.general]

我对此的理解:如果T(在我们的例子中是std::pair>) 不是 CopyInsertableX (在我们的例子中是 std::unordered_map),则 X u(a)X u=a 格式不正确.

CopyInsertable

<块引用>

TCopyInsertableX 的意思是,除了 TMoveInsertable 转换成 X,下面的表达式是良构的:

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

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

我对此的理解: std::pair> 不是CopyInsertable,由于 std::unique_ptr 不可复制:

<块引用>

每个从 unique_ptr 模板实例化的类型为 U 的对象都不是 CopyConstructible 也不是 CopyAssignable.

§20.8.1 [unique.ptr]

并且由于 std::pair> 的复制构造函数是默认的:

<块引用>

pair(const pair&) = default;

§20.3.2 [pairs.pair]

并且由于 std::pair> 具有 std::unique_ptr 类型的成员这一事实代码>:

<块引用>

模板<class T1,class T2>结构对 {

[...]

T2 秒;

§20.3.2 [pairs.pair]

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

<块引用>

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

[...]

  • 类类型 M(或其数组)的非静态数据成员,由于重载决议而无法复制/移动,应用于 M 的相应构造函数, 导致 [...] 一个函数被删除 [...]

§12.8 [class.copy]

std::is_copy_constructible

<块引用>

对于可引用类型T,与is_constructible的结果相同,否则 false.

§20.10.4.3 [meta.unary.prop]

我对此的理解/阅读: std::is_copy_constructible 与 <代码>std::is_constructible,std::unordered_map.

std::is_constructible

<块引用>

给定以下函数原型:

模板<class T>add_rvalue_reference_t<T>create() noexcept;

模板特化 is_constructible<T, Args...> 的谓词条件当且仅当以下变量定义对于某些发明变量 t:

T t(create()...);

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

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

解决方案

你的分析有两个问题.

首先,违反 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<,则此构造函数不应参与重载解析/code>" 以保证 is_copy_constructible 特征的行为方式符合您的要求.标准中没有这样的规范.

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

<小时>

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

g++ --version yields:

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.

Program:

#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 () {   }

Result of compilation:

.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");
 ^

Relevant standardese:

On Containers Being Copyable

For the statements X u(a) and X u=a to be valid, for some container type X, which contains type T, where a is a value of type X:

Requires: T is CopyInsertable into X

§23.2.1 [container.requirements.general]

My understanding of this: If T (in our case std::pair<const int,std::unique_ptr<int>>) is not CopyInsertable into X (in our case std::unordered_map<int,std::unique_ptr<int>>), then X u(a) and X u=a are not well-formed.

On CopyInsertable

T is CopyInsertable into X means that, in addition to T being MoveInsertable into X, the following expression is well-formed:

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

and its evaluation causes the following postcondition to hold: The value of v is unchanged and is equivalent to *p.

My understanding of this: std::pair<const int,std::unique_ptr<int>> is not CopyInsertable, due to the fact std::unique_ptr<int> is not copyable:

Each object of a type U instantiated from the unique_ptr template specified in this subclause [...] is not CopyConstructible nor CopyAssignable.

§20.8.1 [unique.ptr]

And due to the fact that the copy constructor of std::pair<const int,std::unique_ptr<int>> is defaulted:

pair(const pair&) = default;

§20.3.2 [pairs.pair]

And due to the fact that std::pair<const int,std::unique_ptr<int>> has a member of type std::unique_ptr<int>:

template <class T1, class T2> struct pair {

[...]

T2 second;

§20.3.2 [pairs.pair]

And due to the fact that defaulted copy constructors are deleted when it is not the case that all members of a type are CopyConstructible:

A defaulted copy/move constructor for a class X is defined as deleted if X has:

[...]

  • a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution, as applied to M’s corresponding constructor, results in [...] a function that is deleted [...]

§12.8 [class.copy]

On std::is_copy_constructible

For a referenceable type T, the same result as is_constructible<T,const T&>::value, otherwise false.

§20.10.4.3 [meta.unary.prop]

My understanding/reading of this: std::is_copy_constructible<std::unordered_map<int,std::unique_ptr<int>> is the same as std::is_constructible<std::unordered_map<int,std::unique_ptr<int>,std::unordered_map<int,std::unique_ptr<int> &>.

On std::is_constructible

Given the following function prototype:

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

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

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

§20.10.4.3 [meta.unary.prop]

My understanding of this: std::is_constructible<std::unordered_map<int,std::unique_ptr<int>>,std::unordered_map<int,std::unique_ptr<int> &> ought to be std::false_type, not std::true_type, since X u(a) is not well-formed.

My Question

Should the above code be accepted? Is this a GCC/libstdc++ bug, or is there something in the standard I'm missing?

I don't currently have access to Clang or MSVC++, otherwise I'd test on them.

解决方案

There are two problems in your analysis.

First, violating a Requires clause causes undefined behavior (§17.6.4.11 [res.on.required]):

Violation of the preconditions specified in a function’s Requires: paragraph results in undefined behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is violated.

Which means that the library can do anything it wants if you try to copy construct an unordered_map with a non-CopyInsertable element. It doesn't necessarily lead to the program being ill-formed (although it probably will, somewhere deep inside the copy constructor's implementation).

Second, the testing performed by the is_constructible trait is limited to the immediate context (§20.10.4.3 [meta.unary.prop]/p7, emphasis added):

Access checking is performed as if in a context unrelated to T and any of the Args. Only the validity of the immediate context of the variable initialization is considered. [ Note: The evaluation of the initialization can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the "immediate context" and can result in the program being ill-formed. —end note ]

In other words, this basically just considers if there's a matching, accessible, and non-deleted constructor signature, not if instantiating the constructor will result in well-formed code.

The standard would have to specify the copy constructor of containers with something along the lines of "this constructor shall not participate in overload resolution if T is not CopyInsertable into X" to guarantee that the is_copy_constructible trait behaves the way you want it to. There is no such specification in the standard.

As Marc Glisse wrote in the comments, while this isn't mandated by the standard, it can be considered a quality of implementation issue, so a bug report would be reasonable.


Edit: It occurred to me that a requirement to remove the copy constructor from overload resolution for non-CopyInsertable elements is probably not implementable, since that property is specified in terms of a call to allocator_traits<A>::construct(m, p, v) being well-formed and having the required semantics. I do not believe SFINAE could determine the well-formedness of the body of the call to allocator_traits<A>::construct().

这篇关于std::unordered_map<T,std::unique_ptr<U>&gt;可复制?海湾合作委员会错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆