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:
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
isCopyInsertable
intoX
§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.
CopyInsertable
T
isCopyInsertable
intoX
means that, in addition toT
beingMoveInsertable
intoX
, 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 theunique_ptr
template specified in this subclause [...] is notCopyConstructible
norCopyAssignable
.§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 toM
’s corresponding constructor, results in [...] a function that is deleted [...]§12.8 [class.copy]
std::is_copy_constructible
For a referenceable type
T
, the same result asis_constructible<T,const T&>::value
, otherwisefalse
.§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> &>
.
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 variablet
:
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.
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 theArgs
. 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()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With