【问题标题】:Difference in overload selection of user-defined conversion operator between clang and gccclang 和 gcc 用户自定义转换运算符重载选择的区别
【发布时间】:2020-12-16 12:14:55
【问题描述】:

在尝试了解与 this question 相关的编译器行为(gcc 和 clang)时,我只是不明白为什么 gcc 和 clang 之间的第三种情况(如下所示)存在差异。问题不是关于这种转换 API 的正确性(尤其是参考案例)。

能否请您帮助我了解在这种情况下预期的行为(从 c++ 标准的角度来看)是什么?

编辑:如 cmets 中所述,这种行为只能在 -std=c++17 的 clang 中观察到。在此之前,引用转换和 gcc 一样使用。

EDIT2:请注意,正确的行为“似乎”是 gcc,因为隐式 this 参数不是 const,因此首选非常量重载...

这里是示例代码:

struct SInternal {
    SInternal() = default;
    SInternal(const SInternal&) {
        std::cout << "copy ctor" << std::endl;
    }
    int uuid{0};
};

struct S {
 SInternal s;

 S() = default;

 operator SInternal() const {
     std::cout << "copy conversion" << std::endl;
     return s;
 }

 operator SInternal& () {
     std::cout << "ref conversion" << std::endl;
     return s;
 }
};

int main() {
    S s;
    const S s2;
    // 1-
    //SInternal si = s; // no ambiguity, ref conversion
    //SInternal si = s2; // no ambiguity, copy conversion
    // 2-
    // SInternal& si = s; // no ambiguity, ref conversion
    // SInternal& si = s2; // no viable conversion operator SInternal& not const
    // Case 3- WHAT IS THE CORRECT EXPECTED BEHAVIOR HERE?
    SInternal si(s); // no ambiguity but clang uses copy conversion
                     // while gcc uses ref conversion
    //SInternal si(s2); // no ambiguity, copy conversion
    // 4-
    //SInternal si = std::move(s); // no ambiguity ref conversion

    std::cout << "test " << si.uuid << std::endl;
}

DEMO HERE.

感谢您的帮助。

【问题讨论】:

  • 向版本标签发送垃圾邮件只会使确定答案变得更加困难。请仅限于您真正感兴趣的标准。
  • @StoryTeller-UnslanderMonica 感谢您的提示!我只是想接触更广泛的受众,但你说得对,因为我只对最新版本感兴趣
  • 有趣的是,如果-std=c++14,clang 的作用与 gcc 相同。我认为 C++17 中的 RVO 使 operator T() 优于 operator T&amp;() + T(T const&amp;)。而在c++14中,operator T&amp;()+T(T const&amp;)优于operator T()+T(T const&amp;),因为T&amp;T const&amp;的匹配比T更好。参考:over.match.best
  • @RedFog 两个编译器都知道保证复制省略。你不觉得反其道而行之吗? Clang 选择按值版本,然后,当它按值返回时,应用一些优化。先重载分辨率。
  • @nop666 在我看来,RVO 在 clang 的隐式转换序列中被考虑,但在 gcc 中却不是。但是我问的一些律师说,operator T()operator T&amp; 都是 exact match ,所以选择 operator T&amp;() 更好,因为 s 是非常量的。所以也等待更多的答案。

标签: c++ c++17 language-lawyer


【解决方案1】:

这是我迄今为止的研究和 cmets 的善意帮助,试图回答我自己的问题。

非常欢迎在cmets中的任何评论改进答案。

答案与this answerthat one 密切相关,因此问题本身可能是重复的。

gcc 案例

  1. 这是直接初始化的情况(SInternal si(s);)。本案属于dcl.init

否则,如果初始化是直接初始化,或者如果是 复制初始化,其中源的 cv 不合格版本 type 与该类的类相同或派生类 目的地,构造函数被考虑。适用的构造函数 被枚举([over.match.ctor]),并通过 重载决议。如此选择的构造函数被调用到 使用初始化表达式或初始化对象 表达式列表作为其参数。如果没有构造函数适用,或者 重载决议不明确,初始化格式不正确。

结果:考虑SInternal()SInternal(const SInternal&amp;)并选择SInternal(const SInternal&amp;)

  1. 引用必须绑定到 SInternal(SInternal 是被初始化的引用的类型,S 是初始化表达式的类型)。本案属于over.match.ref

    ... 考虑了 S 及其基类的转换函数。那些未隐藏在 S 中并产生类型“对 cv2 T2 的左值引用”(初始化对函数的左值引用或右值引用时)或“cv2 T2”或“对 cv2 T2 的右值引用”的非显式转换函数(当初始化对函数的右值引用或左值引用),其中“cv1 T”与“cv2 T2”是引用兼容的([dcl.init.ref]),是候选函数 ...

结果:候选函数为operator SInternal() constoperator SInternal&amp; ()

  1. 重载分辨率。本案属于[over.match.ref](本案属于over.ics.rank):

结果operator SInternal&amp; () 被选中,因为隐式 this 参数是非常量。

  1. 最终结果伪代码: SInternal(operator SInternal&amp; ())

叮当声

如其他帖子中所述,该行为似乎与 CWG 2327 有关。

如果这是这种行为的编译器实现,则考虑转换函数进行直接初始化并选择operator SInternal () const

最后一点是转换运算符的实现。如果 SInternal 复制 ctor 变得微不足道,则不会调用复制构造函数。如果定义了空的复制构造函数,则调用它 (DEMO)。

这是由于 SInternal 变为 TriviallyCopyable 的事实,因此编译器利用它来使用寄存器进行复制。如果你用更多的数据成员(例如char arr[32];)填充SInternal,它将最终使用memcpy

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-15
    • 1970-01-01
    相关资源
    最近更新 更多