Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 936
Robert Allan Hennigan Leahy Avatar asked Sep 12 '14 07:09

Robert Allan Hennigan Leahy


1 Answers

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

like image 93
T.C. Avatar answered Nov 18 '22 22:11

T.C.