【问题标题】:std::unordered_map::emplace issue with private/deleted copy constructor私有/已删除复制构造函数的 std::unordered_map::emplace 问题
【发布时间】:2013-02-11 08:51:17
【问题描述】:

以下代码使用 gcc 4.7.2 (mingw) 编译良好​​p>

#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&amp;) =delete; 更改为test(test const&amp;) =default;,那么模板错误呕吐物似乎抱怨const test&amp; 不能转换为test(文本here)。都不应该工作吗?或者如果不是,他们不应该都报错吗?

【问题讨论】:

    标签: c++ gcc c++11 mingw unordered-map


    【解决方案1】:

    如果您更仔细地查看模板错误呕吐物,您会在其中看到这块胡萝卜:

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

    这就是问题的线索。

    GCC 4.7.2 不将访问检查作为模板参数推导的一部分(正如 C++03 所要求的那样。)is_convertible 特征是使用 SFINAE 实现的,它依赖于模板参数推导,如果重载解析选择私有构造函数参数推导成功,但随后访问检查失败,因为选择的构造函数是私有的。这是 GCC 4.7 的一个问题,因为它没有被更改为遵循 14.8.2 [temp.deduct] 中的新 C++11 规则:

    -8- 如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。 [ 注意: 访问检查是替换过程的一部分。 ——尾注]

    这是对之前扣除规则的巨大改变,之前那段说过

    -8- 如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。访问检查不作为替换过程的一部分进行。因此,当推导成功时,在实例化函数时仍然可能导致访问错误。

    DR 1170 在 C++0x 进程中进行了相当晚的更改,这使得 SFINAE 在 C++11 中非常棒:)

    GCC 4.8 实现了新规则,因此is_convertible 和类似特征为不可访问的构造函数提供了正确的答案。

    【讨论】:

      【解决方案2】:

      正确答案是乔纳森·韦克利的。我会留下这个,因为它为遇到与insert 相关的类似问题的人提供了有用的信息。


      简短的版本是,这是由 GCC 4.7.2 使用的标准库实现中的问题引起的,这是由于 C++11 标准中使用的误导性措辞造成的。有一个措辞的更改提案,以及 GCC 4.8 中的实现的修复。


      加长版

      This GCC bug entry 报告了一个非常相似的问题,其中使用insert 而不是emplaceinsert 的 libstdc++ 实现遵循标准,该标准说明了 insert 函数(特别是 template &lt;class P&gt; pair&lt;iterator,bool&gt; insert(P&amp;&amp; obj)):

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

      libstdc++ 似乎已经使用enable_if 语句实现了这个要求,该语句检查std::is_convertible&lt;&gt; 所涉及的类型。

      上面链接的错误报告稍后指出确实应该使用std::is_constructible&lt;&gt;,并且应该更改标准中的措辞。它链接到一个 LWG(语言工作组)问题,该问题已经提议对此标准进行更改(LWG issue #2005,请参阅 Portland 2012 条目,以下提议更改的相关部分):

      1. 将 23.5.4.4 [unord.map.modifers] 更改为 p。 1 如图所示:

        template <class P>
        pair<iterator, bool> insert(P&& obj);
        

      [...] 备注:除非 P 可隐式转换为 value_typestd::is_constructible&lt;value_type, P&amp;&amp;&gt;::value 为 true,否则此签名不应参与重载决议。

      提议的更改还指出,上述insert 函数的效果应等同于emplace(std::forward&lt;P&gt;(obj)) 的效果。因此,可以肯定地说,您的问题中描述的问题是完全相同的问题。

      事实上,建议的更改似乎反映在最近的 GCC 4.8 快照中: 当您使用 GCC 4.8 编译代码时,不会执行 is_convertible 检查并且 没有错误消息出现。

      【讨论】:

      • 关闭,但没有 cigar :) LWG 2005 仅适用于 insert 而不是 emplace,程序在 GCC 4.7.2 中失败的原因是它不做访问检查作为模板参数推导(在 C++03 中要求),因此 is_constructible 由于私有构造函数而导致访问失败。 GCC 4.8 实现 C++11 规则并在模板参数推导期间检查访问
      • 您可以通过使用 G++ 4.7 预处理代码(因此它使用 4.7 中的库)然后使用 4.8 编译它来确认差异不是由于标准库中的任何更改,在这种情况下程序有效,证明是编译器而不是库改变了
      猜你喜欢
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 2016-12-19
      • 2020-05-19
      • 2020-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多