【问题标题】:How to avoid 'implicit' calling of a one-parameter constructor in std::pair如何避免在 std::pair 中“隐式”调用单参数构造函数
【发布时间】:2011-11-15 11:20:03
【问题描述】:

最初的问题是如何以安全的方式使用std::map<std::wstring, std::wstring> >,因为相同类型的键和值非常容易出错。所以我决定为这个值创建一个简单的包装器:

    struct ComponentName
    {
      std::wstring name;

      // I want to prohibit any implicit string-ComponentName conversions!!!
      explicit ComponentName(const std::wstring& _name) : name(_name)
      {
      }

      bool operator<(const ComponentName& item_to_compare) const
      {
        return name < item_to_compare.name;
      }
    };

    typedef std::map<std::wstring, ComponentName> component_names_map;

但是下面的代码运行良好!

component_names_map component_names;
// Are you sure that ComponentName's constructor cannot be called implicitly? ;)
component_names_map::value_type a_pair = std::make_pair(L"Foo", L"Bar");

之所以有效,是因为 std::pair&lt;std::wstring, ComponentName&gt; 复制构造函数显式使用 ComponentName 的字符串构造函数来分配 std::pair&lt;std::wstring, std::wstring&gt; 实例。这是绝对合法的操作。但是,它看起来是对 ComponentName 构造函数的“隐式”调用。

所以我知道问题的原因,但是如何避免这种“隐式”wstring-ComponentName 转换? 最简单的方法是不声明字符串构造函数,但是这样会使得 ComponentName 初始化不方便。

【问题讨论】:

  • +1 提出了一个很好的问题。
  • 我不认为您可以阻止某人为您的显式构造函数编写隐式转换包装器...
  • 也许你可以为你的类型写一个工厂函数。
  • 请注意,用户应该编写的代码,即value_type a_pair(L"Foo", L"Bar");,确实是正确禁止的。 C++ 不是要强制执行绝对值,而是要让编写错误代码变得困难,并让编写正确代码变得容易。我认为你的设计在这方面是足够的。
  • 你真正想要的是 friend 的反面——也许是一种 enemy 关键字?

标签: c++ stl


【解决方案1】:

我认为您可以通过为您的类型添加 std::pair 的部分专业化来合法地做到这一点:

namespace std {
    template <typename T>
    struct pair<T,ComponentName> {
       typedef T first_type;
       typedef ComponentName second_type;

       T first;
       ComponentName second;
       // The rest of the pair members:
       // ....
       // Any trick you like to make it fail with assignment/construction from 
       // pair<std::wstring, std::wstring>
    };
}

理由:

第 17.6.4.2.1 节规定了 std 命名空间中特化的基本规则:

"程序可以为任何标准库添加模板特化 仅当声明依赖于 用户定义类型和特化符合标准库 原始模板的要求,并且没有明确说明 禁止”

如果您在 § 20.3 的范围内填写了课程的其余部分,我看不出有任何明确的禁令可以排除这种特殊情况。


替代的,可能是合法的方法:

特化std::is_constructible&lt;ComponentName, std::wstring&gt; 使得value 为假。这被列为不同类型的std::pairs 的赋值运算符和复制构造函数的要求。通过快速扫描,我也看不到任何禁令,但我找不到任何说明需要实现来检查要求。

【讨论】:

  • 有点可怕,但正确的解决方案没有运行时惩罚。谢谢!至于我,我决定使用织物法。它的工作速度有点慢,因为在我的情况下需要 1 次复制,但它更简单。
  • 我认为在这个问题上我会亲自解决map&lt;T,T&gt; 问题
  • @DmitrySapelnikov:我相信你有你的理由。但是,从外部很难判断。对我来说,仅仅为了避免这种极端情况而实施工厂似乎是对优雅的巨大损失。您必须权衡它是否值得,以及是否真的存在这样一个巨大的问题,即人们导致隐式构造错误。
  • @KerrekSB - 同意。您所做的任何事情都无法完全阻止某人在某处传递“错误”值。 map[L"foo"] = L"bar"; cout &lt;&lt; map[L"bar"]; 总是可以用一种或另一种方式编写,即使有工厂投入使用。
【解决方案2】:

问题(在 C++03 中)是大多数标准库实现并不真正符合标准。特别是,该标准规定,当 std::pair&lt;T,U&gt; 从不同的 std::pair&lt;V,W&gt; 构造时,成员是通过隐式转换构造的。问题是实际上很难(如果可能的话)在模板化 pair 构造函数的实现中限制这种转换,因此当前的实现对参数执行显式转换:

template <typename T, typename U>
struct pair {
    // ...
    template <typename V, typename W>
    pair( pair<V,W> const & p ) : first( p.first ), second( p.second ) {} 
};

我实际上写了一篇关于这个特殊案例here 的博客文章,为此我尝试提供适当的转换构造函数here,但该解决方案不符合标准(即它的签名不同于标准要求的)。

注意:在 C++11(§20.3.2p12-14)中,这种隐式转换也是被禁止的(来自 FDIS):

template<class U, class V> pair(pair<U, V>&& p);

要求:is_constructible::value 为真,is_constructible::value 为真。

效果:构造函数首先用 std::forward(p.first) 初始化,其次用 std::forward(p.second) 初始化。

备注:除非 U 可隐式转换为 first_type 且 V 可隐式转换为 second_type,否则此构造函数不应参与重载决议。

在 p9-11 中存在与 template&lt;class U, class V&gt; pair(const pair&lt;U, V&gt;&amp; p); 等效的等效限制(如果类型不是可移动的

【讨论】:

  • 有趣的是,n3290 的措辞似乎比您在博客上引用的措辞强。它现在声明 “除非U 可隐式转换为first_type 并且V 可隐式转换为second_type,否则此构造函数不应参与重载决议”,这似乎使这种行为成为缺陷,因为它肯定参与了重载解决。
【解决方案3】:

简单:

enum FromString { fromString };

ComponentName( FromString, std::wstring const& aName)
    : name( aName )
{}

【讨论】:

  • 这确实很简单,但也很丑陋和令人困惑。可悲的是,我也没有更好的主意。 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 2017-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多