【问题标题】:GCC can't differentiate between operator++() and operator++(int)GCC 无法区分 operator++() 和 operator++(int)
【发布时间】:2021-12-01 00:30:53
【问题描述】:
template <typename CRTP>
struct Pre {
    CRTP & operator++();
};

template <typename CRTP>
struct Post {
    CRTP operator++(int);
};

struct Derived
    : Pre<Derived>
    , Post<Derived>
{};

int main() {
    Derived d;
    d++;
    ++d;
}

我从 GCC 收到这些错误:

<source>: In function 'int main()':
<source>:18:10: error: request for member 'operator++' is ambiguous
        d++;
        ^~
<source>:8:14: note: candidates are: CRTP Post<CRTP>::operator++(int) [with CRTP = Derived]
        CRTP operator++(int);
            ^~~~~~~~
<source>:3:16: note:                 CRTP& Pre<CRTP>::operator++() [with CRTP = Derived]
        CRTP & operator++();
                ^~~~~~~~
<source>:19:11: error: request for member 'operator++' is ambiguous
        ++d;
        ^
<source>:8:14: note: candidates are: CRTP Post<CRTP>::operator++(int) [with CRTP = Derived]
        CRTP operator++(int);
            ^~~~~~~~
<source>:3:16: note:                 CRTP& Pre<CRTP>::operator++() [with CRTP = Derived]
        CRTP & operator++();
                ^~~~~~~~

前减和后减运算符会导致类似的错误。 Clang 没有这样的错误。有什么想法可能是错误的或如何解决这个问题?

【问题讨论】:

  • using Pre::operator++; using Post::operator++; 有效,但我想它违背了您的 CRTP 的目的......
  • fwiw 也提供了实现,也没有 crtp gcc reports the error
  • @Quentin 将 using 声明放入帮助模板 PrePost : Pre, Post
  • 对我来说 gcc 的行为似乎是正确的。函数 operator ++ 的调用不应编译,因为不清楚名称 operator ++ 指的是哪个函数。
  • 从某种意义上说,语言本身存在需要解决的不一致问题,这并不是缺陷。如果你是的话,这只是一个带有不幸后果的设计选择,一个口语缺陷。

标签: c++ operator-overloading multiple-inheritance ambiguous


【解决方案1】:

必须首先进行名称查找。在这种情况下,名称为operator++

[basic.lookup](强调我的)

1 名称查找规则统一适用于所有名称(包括 typedef 名称([dcl.typedef]),名称空间名称([basic.namespace]), 和类名 ([class.name])),只要语法允许这样的名称 在特定规则讨论的上下文中。名称查找关联 使用带有该名称声明 ([basic.def]) 的名称。 名称 查找应找到名称的明确声明(参见 [class.member.lookup])。名称查找可能关联多个 如果发现名称是函数名,则使用名称声明; 据说这些声明形成了一组重载函数 ([超载])。 重载解析 ([over.match]) 发生在 名称查找成功。访问规则(子句 [class.access]) 只考虑一次名称查找和函数重载解析 (如果适用)已成功。只有在名称查找后,功能 重载解析(如果适用)和访问检查已成功 是进一步使用的名称声明引入的属性 在表达式处理中(子句 [expr])。

并且只有在查找明确时,才会继续进行重载解析。在这种情况下,名称位于两个不同类的范围内,因此即使在重载决议之前也存在歧义。

[class.member.lookup]

8 如果明确找到重载函数的名称, 重载决议([over.match])也发生在访问之前 控制。歧义通常可以通过限定名称来解决 它的类名。 [ 例子:

struct A {
  int f();
};

struct B {
  int f();
};

struct C : A, B {
  int f() { return A::f() + B::f(); }
};

—结束示例]

这个例子几乎总结了[class.member.lookup]前面段落中相当长的查找规则。您的代码有歧义。 GCC 是正确的报告它。


至于解决这个问题,cmets 的人们已经提出了解决方法的想法。添加辅助 CRTP 类

template <class CRTP>
struct PrePost
    : Pre<CRTP>
    , Post<CRTP>
{
    using Pre<CRTP>::operator++;
    using Post<CRTP>::operator++;
};

struct Derived : PrePost<Derived> {};

现在可以在单个类的范围内找到该名称,并命名两个重载。查找成功,可以继续进行重载解析。

【讨论】:

  • Fwiw,MS VS2015 (19.00.24215.1) 通过 IntelliNonsense 抱怨歧义,但随后仍将代码编译成功(没有警告或错误)并在运行时执行希望的成员.
  • @StoryTeller 实际上,对于f()f(int) 的“正常”方法,clang++ 抱怨:“错误:在不同类型的多个基类中找到成员'f'”和添加“注意:通过不明确的名称查找找到的成员”。 GCC 实际上在这里更加一致。
  • 好的。因此,这似乎是语言中的一种缺陷(d++++d 指的是相同的运算符/函数名称),gcc 忠实地实现了它,而 clang 和 VS17 似乎实现了用户明显想要的东西(即使没有警告)。
  • @Walter - 差不多。如果我们在重载方面采取了类似 Python 的路径,那么它可能更容易在所有方面工作。唉,选择了别的东西。
  • @PiotrNycz - 除了被特殊语法调用之外,重载的运算符常规函数。事实上,它们也可以像普通函数一样被调用。 d.operator++(0) 会导致这种歧义,d++ 也应该如此,因为两者完全相同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-16
相关资源
最近更新 更多