Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::unordered_map::emplace issue with private/deleted copy constructor

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?

like image 721
user657267 Avatar asked Feb 11 '13 08:02

user657267


2 Answers

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.

like image 114
Jonathan Wakely Avatar answered Sep 28 '22 00:09

Jonathan Wakely


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):

  1. 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.

like image 31
jogojapan Avatar answered Sep 27 '22 23:09

jogojapan