The following code compiles fine with gcc 4.7.2 (mingw)
#include <unordered_map>
#include <tuple>
struct test
{
test() =default;
private:
test(test const&) =delete;
};
int main()
{
std::unordered_map<char, test> map;
map.emplace(
std::piecewise_construct,
std::forward_as_tuple('a'),
std::forward_as_tuple()
);
}
If I change the copy constructor in test
from test(test const&) =delete;
to test(test const&) =default;
however, the template error vomit seems to complain about const test&
not being convertible to test
(text here). Shouldn't either work? Or if not, shouldn't they both give an error?
If you look at the template error vomit more carefully you'll see this chunk of carrot in it:
test.exe.cpp:8:3: error: 'constexpr test::test(const test&)' is private
This is the clue to the problem.
GCC 4.7.2 doesn't do access checking as part of template argument deduction (as was required by C++03.) The is_convertible
trait is implemented using SFINAE, which relies on template argument deduction, and if overload resolution chooses a private constructor argument deduction succeeds, but then access checking fails because the chosen constructor is private. This is a problem with GCC 4.7 because it hadn't been changed to follow the new C++11 rule in 14.8.2 [temp.deduct] which says:
-8- If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [ Note: Access checking is done as part of the substitution process. —end note ]
This is a huge change to the previous deduction rules, previously that paragraph said
-8- If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. Access checking is not done as part of the substitution process. Consequently, when deduction succeeds, an access error could still result when the function is instantiated.
The change was made quite late in the C++0x process by DR 1170, and makes SFINAE totally awesome in C++11 :)
GCC 4.8 implements the new rules, so is_convertible
and similar traits give the right answer for inaccessible constructors.
The correct answer is Jonathan Wakeley's. I'll leave this as it provides useful information for people who have a similar problem related to insert
.
The short version is that this is caused by a problem in the Standard Library implementation used by GCC 4.7.2, which resulted from a misleading wording used in the C++11 Standard. There is a change proposal for the wording, as well as a fix of the implementation in GCC 4.8.
Long version
This GCC bug entry reports a very similar issue, where insert
is used instead of emplace
. The libstdc++ implementation of insert
follows the Standard, which states about the insert
function (specifically, template <class P> pair<iterator,bool> insert(P&& obj)
):
(§23.5.4.4/5) Remarks: This signature shall not participate in overload resolution unless P is implicitly convertible to
value_type
.
libstdc++ seems to have implemented this requirement using an enable_if
statement that checks std::is_convertible<>
for the types involved.
The bug report linked above later states that really std::is_constructible<>
should have been used, and the wording in the Standard should be changed. It links to an LWG (language working group) issue, which proposes a change to the Standard for this already (LWG issue #2005, see the Portland 2012 entry, relevant portion of the proposed change below):
Change 23.5.4.4 [unord.map.modifers] around p. 1 as indicated:
template <class P> pair<iterator, bool> insert(P&& obj);
[...] Remarks: This signature shall not participate in overload resolution unless P is
implicitly convertible to value_typestd::is_constructible<value_type, P&&>::value
is true.
The proposed change also states that the effect of the insert
function described above should be equivalent to that of emplace(std::forward<P>(obj))
. It is therefore probably safe to say that the issue described in your question is exactly the same problem.
And indeed, the proposed changes seem to be reflected in recent GCC 4.8 snapshots: When you compile your code with GCC 4.8, the is_convertible
check isn't performed and no error message appears.
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