【问题标题】:Possible bug in clang or gcc because of deleted templated lvalue conversion operator?由于删除了模板化左值转换运算符,clang 或 gcc 中可能存在错误?
【发布时间】:2021-05-15 08:49:15
【问题描述】:

这是this 问题的后续。 我玩过这段代码并使用了 clang trunk 和 gcc trunk:

struct A
{   
};

struct T
{
    A a;
    operator A() { return a; }
    
    template <typename T> operator const T&() = delete;
};

struct C
{   
    A a;
};

int main()
{
    C c;
    T t;

    c.a = t;
}

Demo

Clang 没有什么可抱怨的,但 gcc 有:

<source>: In function 'int main()':
<source>:23:11: error: use of deleted function 'T::operator const T&() [with T = A]'
   23 |     c.a = t;
      |           ^
<source>:10:27: note: declared here
   10 |     template <typename T> operator const T&() = delete;
      |  

                     ^~~~~~~~

那么,哪个编译器是对的,哪个是错的?

我想看看 gcc 是错误的,但是我该如何克服这个错误呢?

【问题讨论】:

  • 我添加了language-lawyer 标签,因为我必须删除一个,而且编译器之间的差异不一定是一个错误,所以我删除了compiler-bug
  • @largest_prime_is_463035818 我认为这很可能是它们中的任何一个的编译器错误,因为似乎很难相信任何关于重载解决方案是依赖于实现的(至少我想不出一个很好的理由应该是)。
  • 现在我明白了……我的赌注是 gcc 有一个错误。您可以进一步简化:godbolt.org/z/fo8WKs
  • 但是这玩意不是很老了吗?你甚至不需要 C++11 编译器?
  • @orlp 我可以删除一个不同的标签,但只能有 5 个。我将问题理解为询问标准对此有何规定,以及它是否是一个错误将随之而来。 (顺便说一句,编译器错误与否不是我评论的重点,我只是想为 OP 留下关于我更改的内容和原因的说明,以便他们可以回滚、编辑或执行他们认为最合适的操作)

标签: c++ gcc type-conversion operators language-lawyer


【解决方案1】:

Clang 和 GCC 都是错误的,但至少 GCC 会停止编译,所以稍微好一点。

A 是一个类类型,因此分配给它会通过operator= 重载。你没有提供一个,所以编译器提供了两个的效果

A& operator=(A const&) = default;
A& operator=(A&&) = default;

该引用参数需要从表达式c.a = t 中的t 初始化。对于operator=,引用可以明确地绑定。

[dcl.init.ref]

5 对类型“cv1 T1”的引用由以下表达式初始化 输入“cv2 T2”如下:

  • 如果引用是左值引用和初始化表达式

    • [...]
    • 具有类类型(即T2是类类型),其中T1与T2没有引用相关,可以转换为类型的左值 “cv3 T3”,其中“cv1 T1”与“cv3 T3”引用兼容(这 通过枚举适用的转换来选择转换 函数([over.match.ref])并通过 重载决议),

    然后将引用绑定到...的左值结果 在第二种情况下转换(或者,在任何一种情况下,转换为适当的 对象的基类子对象)。

  • 否则,如果初始化表达式

    • [...]
    • 具有类类型(即 T2 是类类型),其中 T1 与 T2 没有引用相关,可以转换为右值或函数 “cv3 T3”类型的左值,其中“cv1 T1”与 “cv3 T3”(参见 [over.match.ref]),

    then ...调用第二种情况的转换结果 转换后的初始化程序。如果转换后的初始值设定项是纯右值, 它的类型 T4 被调整为类型“cv1 T4” ([conv.qual]) 并且 应用临时实现转换 ([conv.rval])。在任何 在这种情况下,引用绑定到生成的 glvalue(或绑定到 适当的基类子对象)。

关于为这两种情况构建候选集的主题,标准说

[over.match.ref]

1在[dcl.init.ref]规定的条件下,引用可以 直接绑定到应用转换函数的结果 初始化表达式。过载分辨率用于选择 要调用的转换函数。假设“引用 cv1 T” 是被初始化的引用的类型,“cv S”是类型 初始值设定项表达式,具有 S 类类型,候选 功能选择如下:

  • 考虑了 S 及其基类的转换函数。那些不隐藏在 S 中的非显式转换函数 并产生类型“对 cv2 T2 的左值引用”(在初始化 对函数的左值引用或右值引用)或“cv2 T2”或 “对 cv2 T2 的右值引用”(初始化右值引用或 对函数的左值引用),其中“cv1 T”是 与“cv2 T2”的引用兼容,是候选函数。为了 直接初始化,那些显式转换函数 不隐藏在 S 中并产生类型“对 cv2 T2 的左值引用”(当 初始化对函数的左值引用或右值引用) 或“对 cv2 T2 的右值引用”(初始化右值引用时 或对函数的左值引用),其中 T2 与 T 的类型相同或 可以通过限定转换转换为 T 类型,也是 候选函数。

该项目符号需要一些工作才能正确解析,但它基本上描述了可能适用于此处的两种不相交的情况之一:

  1. 对 T 的左值引用初始化
    • 候选函数是那些产生“对 cv2 T2 的左值引用”的函数。
  2. 对 T 的右值引用初始化
    • 候选函数是那些产生“cv2 T2”或“对 cv2 T2 的右值引用”的函数。

对于operator=(A const&amp;),我们在#1 中,并有一个合成的operator A const&amp;() 作为唯一的候选者。对于operator=(A&amp;&amp;) 其案例#2,非模板operator A() 是唯一的候选者。无论哪种方式,我们都有一个明确的隐式转换序列,其中包含一个用户定义的转换,它绑定了operator= 的引用参数。

但是根据[over.match.best] 中的规则,现在operator= 都不是比另一个更好的可行功能。根据[over.ics.rank] 中的部分排序,这两种转换都不是更好。

这意味着由于对operator= 的模棱两可的调用,该程序应被声明为格式错误。但是,GCC 和 Clang 都会出错(虽然不是 MSVC)1。 Clang 支持 A&amp;&amp; 重载,而 GCC 支持 A const&amp; 并发出使用已删除转换函数的诊断。但这不是由于任何标准的强制行为。理想情况下,他们都应该报告对operator= 的调用不明确。


1 - A comparison of different compiler behaviors, with a reduced example.

【讨论】:

  • 如果我理解正确的话,这意味着没有办法禁止左值转换?
  • @Juergen - 我可能是错的,但我不相信有。并非不影响一些非常基本的结构。就个人而言,我还没有看到用户定义的对引用的转换并没有做意外的事情。如果它变得过于复杂,我通常会避免转换运算符。顺便说一句,为什么将转换结果绑定到左值引用是个问题?
  • 想象一个转换为 std::optional 的类。转换时,我无法返回副本,因为 std::optional& opt(myConverter) 会失去更新基础值的能力,不是吗?
  • 提供的代码禁止左值转换...但赋值需要左值...
  • @StoryTeller-UnslanderMonica:奇怪,显式转换也失败了Demo(而隐式(来自链接问题)有效)......有什么区别?
猜你喜欢
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
  • 2021-05-15
  • 2012-12-22
  • 1970-01-01
  • 2011-12-06
  • 1970-01-01
  • 2020-10-14
相关资源
最近更新 更多