【问题标题】:Non-const copy constructor and implicit conversions on return value非常量复制构造函数和返回值的隐式转换
【发布时间】:2011-09-18 02:41:20
【问题描述】:

考虑以下 C++ 代码:

struct B { };
struct A
{
        A(int);
        A(A&); // missing const is intentional
        A(B);
        operator B();
};

A f()
{
        // return A(1); // compiles fine
        return 1; // doesn't compile
}

这在 MSVC++ 2010 上编译得很好(事实上,在 MSVC 上,如果我完全删除 B 它甚至可以工作)。它不在 GCC 4.6.0 上:

conv.cpp: In function ‘A f()’:
conv.cpp:13:9: error: no matching function for call to ‘A::A(A)’
conv.cpp:13:9: note: candidates are:
conv.cpp:6:2: note: A::A(B)
conv.cpp:6:2: note:   no known conversion for argument 1 from ‘A’ to ‘B’
conv.cpp:5:2: note: A::A(A&)
conv.cpp:5:2: note:   no known conversion for argument 1 from ‘A’ to ‘A&’
conv.cpp:4:2: note: A::A(int)
conv.cpp:4:2: note:   no known conversion for argument 1 from ‘A’ to ‘int’

让我感到困惑的是no known conversion for argument 1 from ‘A’ to ‘B’ 的消息。考虑到A::operator B() 的定义非常明确,这怎么可能是真的?

【问题讨论】:

  • 我不确定B 与这里有什么关系。您正在将int 转换为A,不是吗?所以你故意从复制构造函数中省略了const,所以它不起作用。你预计会发生什么?
  • 可能无法将 1 转换为 B?错误消息显示for argument [number] 1
  • @littleadv:如果B 被删除,即使return A(1) 也不再起作用。即使在没有 const 复制构造函数的情况下,A::operator B()A::A(B) 的存在也允许从函数返回 A。此技巧在 C++ 标准中用于实现 std::auto_ptr:参见 ISO/IEC 14882:2003, §20.4.5.2(此处为 auto_ptr = A, auto_ptr_ref = B)。
  • @Vlad:我不确定你想说什么。 GCC 成功将1 转换为A,因为第一条错误消息是关于A::A(A),而不是A::A(int)。然而,它无法将A 转换为B,尽管A::operator B()A::A(B) 是明确定义的。我试图理解为什么。
  • 好吧,@DeadMG 的回答对我来说看起来不错。

标签: c++ gcc copy-constructor implicit-conversion


【解决方案1】:

我不认为像 DeadMG 所指出的那样,“要靠自己计算的步骤太多”是原因。我有 3-4 次转换的构造,编译器总是能很好地解决它们。

我认为问题在于不允许编译器以自己的名义将const 引用转换为非constreference(只有当您通过强制转换明确告诉它时才允许这样做)。
而且由于传递给复制构造函数的临时对象的引用是const,而复制构造函数不是,所以它没有找到合适的函数。

编辑:我没有找到任何“真实”代码(参见下面的 cmets),但构建了一个多锯齿形转换示例,该示例在 gcc 4.5 下实际编译没有错误。请注意,这与 -Wall -Wextra 也可以很好地编译,坦率地说,这让我感到惊讶。

struct B
{
    signed int v;
    B(unsigned short in) : v(in){}
};

struct C
{
    char v;
    C(int in) : v(in){}
};

struct A
{
    int v;
    A(B const& in) : v(in.v){}
    operator C() { return C(*this); }
};

enum X{ x = 1 };

int main()
{
    C c = A(x);
    return 0;
}

【讨论】:

  • 这就是阻止编译器使用A::A(A&) 的原因。但这并不妨碍它使用A::A(B)A::operator B(),因为const 在传递值时是无关紧要的。
  • “我有 3-4 次转换的构造,编译器总是能很好地解决它们。”你用的是什么编译器?我的代码示例使用 MSVC 编译得很好,这似乎表明 GCC 和 MSVC 在这方面的行为不同。
  • 如果您谈论的是用户定义的转换(构造函数和转换函数),那么这与 C++ 标准相矛盾。参见 ISO/IEC 14882:2003,第 12.3.4 节:“最多一个用户定义的转换(构造函数或转换函数)隐式应用于单个值。”。我怀疑 MSVC++ 过于宽松,有时确实如此。
  • @e-t172:我正在使用 gcc-minw 4.5(“TDM”版本)并且在此之前一直在使用 4.4 版本。让我看看我是否能找到一些可以进行多次转换的工作代码(尽管发现这是一个真正的挑战......不知道要搜索什么)。
  • 我也是这么认为的,但是错误信息很混乱error: no matching function for call to ‘A::A(A)’
【解决方案2】:

错误在被拒绝的候选人名单上很清楚。问题是涉及 C++ 语言中用户定义转换的隐式转换序列仅限于单个用户定义转换:

§13.3.3.1.2 [over.ics.user]/1 用户定义的转换序列由初始标准转换序列和用户定义的转换 (12.3) 和第二个标准转换序列组成。

标准转换序列在§4[conv]中定义:

[...] 标准转换序列是按以下顺序进行的标准转换序列

  • 从以下集合中进行零次或一次转换:左值到右值的转换、数组到指针的转换和函数到指针的转换。

  • 以下集合中的零个或一个转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、指向成员的指针转换和布尔转换。

  • 零或一次资格转换。

问题是您的代码无法通过应用单个用户定义的转换从点 a) int 右值到点 b) B

特别是,所有可用的转换序列都以用户定义的转换(隐式构造函数A(int))开始,该转换产生A 右值。从那里,右值不能绑定到非常量引用以调用A::A( A& ),因此该路径被丢弃。所有其他路径都需要第二次用户定义的转换,这是不允许的,事实上,唯一能让我们到达 b) 点的其他路径需要另外两次用户定义的转换,总共 3 次。

【讨论】:

  • DeadMG 的答案,但更详细,所以我给你打勾。谢谢!
【解决方案3】:

因为您不能进行多次隐式转换。你必须去A::A(A::A(int)::operator B()) 才能完成这项工作,而编译器自己解决的步骤太多了。

【讨论】:

  • 我做了更多的研究,似乎 C++ 标准与您一致:ISO/IEC 14882:2003, §12.3.4 “最多一个用户定义的转换(构造函数或转换函数) 隐式应用于单个值。"。
  • 请务必注意,您不允许进行多次用户定义转换,而不是隐式转换。还有其他标准转换是隐式的并且可以链接。 A foo() { return 5.0; } 将开始从 double 转换为 int(隐式浮点转换),然后是用户定义的转换 A(int)。您可以计算出其他示例,其中可能包括最多 3 个非用户定义的转化,后跟 1 个用户定义的转化,以及在用户定义的转化之后最多 3 个其他非用户定义的转化。
【解决方案4】:

错误列出了所有潜在要使用的候选者,以及它们不能被使用的原因。它列出了来自B 的转换,因为它是构造函数之一,但它不知道在这种情况下如何使用它,所以它没有。

【讨论】:

  • 我知道“它不知道如何使用它”。这基本上就是错误消息所说的内容。我的问题是为什么
  • @e-t172 - 好吧,也许下次你会更好地表达这个问题。
猜你喜欢
  • 2020-06-20
  • 2023-03-20
  • 2014-08-05
  • 1970-01-01
  • 2019-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多