std::unordered_map::emplace问题与私有/已删除的复制构造函数。

12

以下代码可以使用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()
    );
}

如果我把test的拷贝构造函数从test(test const&) =delete;改为test(test const&) =default;,然而,模板错误提示好像抱怨const test&不能转换为test (文本在这里)。难道两者都可以吗?或者,如果不行,它们两个都应该报错吧?

2个回答

12

如果您仔细查看模板错误提示,您会在其中看到这个胡萝卜块:

test.exe.cpp:8:3: error: 'constexpr test::test(const test&)' is private

这是问题的线索。

GCC 4.7.2在模板参数推导时不执行访问控制检查(这是C++03所要求的)。使用SFINAE实现了is_convertible特性,它依赖于模板参数推导。如果重载决议选择一个私有构造函数,那么就会成功进行参数推导,但随后访问控制检查会失败,因为所选的构造函数是私有的。这是GCC 4.7中存在的一个问题,因为它还没有改变以遵循新的C++11规则14.8.2 [temp.deduct]:

-8- 如果替换得到的类型或表达式无效,则类型推断失败。无效的类型或表达式是指如果使用替换的参数写出将会非法的类型或表达式。[注意: 访问控制检查是作为替换过程的一部分而完成的。 —end note]

这是以前推导规则的一个巨大变化,在此之前该段落说:

-8- 如果替换得到的类型或表达式无效,则类型推断失败。无效的类型或表达式是指如果使用替换的参数写出将会非法的类型或表达式。访问控制检查不是作为替换过程的一部分而完成的。因此,当推导成功时,在实例化函数时仍然可能会出现访问错误。

这个变化是由DR 1170在C++0x进程的相当晚期进行的,使得SFINAE在C++11中变得非常棒:)

GCC 4.8实现了新规则,因此is_convertible和类似的特性对于不可访问的构造函数给出正确答案。


4
正确答案是Jonathan Wakeley的。这提供了有用的信息,适用于遇到与insert相关的类似问题的人。
简短地说,这是由GCC 4.7.2使用的标准库实现中的问题引起的,这是由C++11标准中使用的误导性措辞导致的。有一个更改建议用于措辞,以及在GCC 4.8中实现的修复方法。
长话短说,这个GCC bug报告报告了一个非常相似的问题,其中insert被用来代替emplaceinsert的libstdc++实现遵循标准,该标准规定了insert函数(具体而言,是template <class P> pair<iterator,bool> insert(P&& obj)):

(§23.5.4.4/5) 备注:除非P可以隐式转换为value_type,否则此签名不得参与重载决议。

libstdc++似乎使用一个检查涉及类型的std::is_convertible<>enable_if语句来实现此要求。

上面链接的错误报告随后指出,实际上应该使用std::is_constructible<>,并且应更改标准中的措辞。它链接到一个LWG(语言工作组)问题,该问题已经为此提出了标准的更改建议(LWG问题#2005,请参阅所提出的更改的相关部分下的Portland 2012条目):

  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.

建议的更改还规定,上述所述的insert函数的影响应等同于emplace(std::forward<P>(obj))的影响。因此,可以说你提出的问题是完全相同的问题。
而且,最新的GCC 4.8快照似乎反映了建议的更改:当使用GCC 4.8编译代码时,不会执行is_convertible检查,也不会出现错误消息。

1
差一点,但还不够好 :) LWG 2005只适用于insert而不是emplace。GCC 4.7.2的程序失败的原因是它在模板参数推导过程中没有进行访问检查(这是C++03所要求的),所以is_constructible由于私有构造函数而出现访问失败。GCC 4.8实现了C++11的规则,并在模板参数推导期间进行了访问检查。 - Jonathan Wakely
2
您可以通过使用 G++ 4.7 预处理代码(因此它使用来自 4.7 的库),然后使用 4.8 进行编译,以确认差异不是由于标准库的任何更改而引起的,在这种情况下,程序可以正常工作,证明是编译器而不是库发生了变化。 - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接