【问题标题】:Why std::pair calls explicit constructors in assignment为什么 std::pair 在赋值中调用显式构造函数
【发布时间】:2011-07-20 10:46:14
【问题描述】:

考虑以下代码:

#include<iostream>
#include<utility>


struct Base
{
    int baseint;
};

struct Der1 : Base
{
    int der1int;
    Der1() : der1int(1) {}
    explicit Der1(const Base& a) : Base(a), der1int(1)
    {
        std::cerr << "cc1" << std::endl;
    }
};

struct Der2 : Base
{
    int der2int;
    Der2() : der2int(2) {}
    explicit Der2(const Base& a) : Base(a), der2int(2)
    {
        std::cerr << "cc2" << std::endl;
    }
};


template <typename T, typename U>
struct MyPair
{
    T first;
    U second;
};

int main()
{
    Der1 d1;
    Der2 d2;

    std::pair<Der1, int> p1;
    std::pair<Der2, int> p2;

    p1 = p2; // This compiles successfully

    MyPair<Der1, int> mp1;
    MyPair<Der2, int> mp2;

    mp1 = mp2; // This will raise compiler error, as expected.
}

在 GCC 4.5.2 下测试

原因在于std::pair来源:

  /** There is also a templated copy ctor for the @c pair class itself.  */
  template<class _U1, class _U2>
    pair(const pair<_U1, _U2>& __p)
    : first(__p.first),
      second(__p.second) { }

这种行为是否符合 C++ 标准?乍一看,它看起来不一致且违反直觉。 STL 的其他实现是否以同样的方式工作?

【问题讨论】:

  • 您到底为什么认为这是不一致和违反直觉的?你反对什么?我认为这是完全符合预期的行为。
  • 您没有显示 std::pair 赋值运算符。那是相关的。
  • std::pair 赋值运算符未定义或我是盲人。
  • @konrad-rudolph - 我没想到在我进行赋值时会调用显式构造函数。
  • @Steve:哦,抱歉,我误解了对explicit 的强调——我要删除我的评论!

标签: c++ constructor standards-compliance explicit


【解决方案1】:

我不确定我是否理解这个问题,但基本上你是在问为什么两个不相关的std::pair 可以隐式转换,即使实例化类型不可隐式转换。也就是说,为什么实例化类型的 implicitly 可转换属性不会传播到该对。

该标准没有为std::pair 模板提供显式赋值运算符,这意味着它将使用隐式生成的赋值运算符。为了能够分配成对的可转换类型,它依赖于允许从std::pair&lt;A,B&gt;std::pair&lt;C,D&gt;隐式 转换的模板化构造函数,其行为在§20.2.2 [lib .pairs]/4

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

效果:从参数的相应成员初始化成员,执行隐式配置 需要的版本。

标准似乎只要求实现使用隐式转换,而在这个特定的实现中转换实际上是显式的,这似乎与标准的措辞相矛盾。

【讨论】:

  • 不过,您将如何实际实现它?将__p.first 放在初始化列表中,调用显式构造函数。但是如果你不这样做,this-&gt;first 需要是默认可构造的,这也不符合。 std::pair 是否需要魔术编译器内部来避免显式转换,还是我错过了诀窍?
  • @Steve Jessop:我正在考虑这个问题,但这似乎并不容易。我猜该解决方案将模板元编程来检测类型是否可以隐式转换,如果不是,则使用静态断言。这应该很容易实现,但会给实现增加一些复杂性。现在我想起来了,这可能会破坏从std::pair&lt;A,B&gt;std::pair&lt;C,D&gt; 的显式转换,所以这也可能不是解决方案
  • 这是一个非常好的问题。不幸的是,我不知道任何行为正确的 STL 实现。
  • @rawicki:我不认为它真的可以做到。我有implemented SFINAE 方法(并借此机会开始另一个博客--我在这里自我提升感到羞耻)并且因为它是一个构造函数(无返回类型),所以必须使用额外的构造函数参数触发失败,这违反了标准合规性。
【解决方案2】:

作为类 std::pair 构造函数的一部分

template<class T1, T2>
class pair
{
public:

    template<class _U1, class _U2>
    pair(const pair<_U1, _U2>& __p)
         : first(__p.first),
           second(__p.second)
    { } 

};

不是复制构造函数,而是从任何pair&lt;_U1, _U2&gt;pair&lt;T1, T2&gt; 的转换构造函数。这适用于firstsecond 成员可转换为另一对的相应成员的情况。

每个成员单独转换是按照标准的。

【讨论】:

  • 根据标准分别转换每个成员:是的,但是标准在这句话中添加了隐式,在这种情况下实现是进行显式转换,这就是问题本身的原因。
  • @David 你会怎么写这个构造函数?
  • @David - 好的,我可能专注于这部分不是赋值或复制构造函数,而错过了问题的explicit 部分。 C++0x 说如果成员不是隐式可转换的,则不应考虑此构造函数,但 C++98 标准没有这样说,并且无论如何也没有提供任何工具来决定它。
【解决方案3】:

这确实应该是一个评论,但我更喜欢有一些空间来输入它。

所以,假设我们有两种类型:

typedef std::pair<A,B> pairAB;
typedef std::pair<S,T> pairST;

现在我想将一个分配给另一个:

pairAB x;
pairST w;

x = w; // how?

由于std::pair 没有显式赋值运算符,我们只能使用默认赋值pairAB &amp; operator=(const pairAB &amp;)。因此我们调用隐式转换构造函数,相当于:

x = pairAB(w);  // this happens when we say "x = w;"

不过已经指出,这个转换构造函数调用了显式成员构造函数:

pairAB(const pairST & other) : first(other.first), second(other.second) { }

因此,我们确实对每个成员单独使用显式转换。

【讨论】:

    【解决方案4】:

    快速回答:因为标准规定应该这样做。

    当然,您的下一个问题是:为什么标准会这样说?

    想象一下这条线,我认为你同意它应该可行:

    std::pair<long, long> x = std::make_pair(3, 5);
    

    但由于35 是整数,我们试图将std::pair&lt;int, int&gt; 分配给std::pair&lt;long, long&gt;。如果没有模板化的构造函数和模板化的赋值运算符,它就会失败,正如您的 MyPair 所证明的那样。

    所以回答你的问题:模板化的构造函数是为了方便。每个人都希望能够将 int 分配给 long。因此,能够分配例如是合理的。一对整数到一对长整数。

    【讨论】:

    • 我不认为标准规定了这一点。看我的回答。我可以产生的唯一报价来自 20.2.2,它要求 std::pair&lt;A,B&gt; 可以隐式转换为 std::pair&lt;C,D&gt; 根据需要执行 隐式 转换。实现实际上是在执行显式转换。
    • 但是int 可以隐式转换为longDer2 是显式但隐式转换为Der1。我认为如果X 不能隐式转换为Y,那么pair&lt;X,X&gt; 不应隐式转换为pair&lt;Y,Y&gt; 是有道理的。这个实现只是没有实现这一点(我无法立即想到它会如何)。
    • Der2 可隐式转换为 Base。还有一个Der1 构造函数采用Base。转换类型时允许一次隐式转换和一次自定义转换,因此允许从Der2Der1 的转换。由于有显式初始化(​​没有它怎么可能?),explicit 不会禁止这种转换。
    • 那么“隐式”一词在 20.2.2 中意味着什么,如果不排除显式转换?这是否意味着从UT1 的转换可以是显式的,但附加会根据需要执行隐式转换?
    猜你喜欢
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-25
    • 1970-01-01
    相关资源
    最近更新 更多