通过引入不相关的调用运算符-clangvsgcc奇怪地解决了模棱两可的调用运算符重载

考虑实现一个overload_two类的任务,以便编译以下代码:

int main()
{
    overload_two o{
        [](int) { return 1; },
        [](char){ return 2; }
    };

    static_assert(o(0) + o('a') == 3);
}

第一次尝试看起来像这样:

template <typename F, typename G>
struct overload_two : F, G
{
    overload_two(F f, G g) : F{f}, G{g} { }
};

当然,这会失败并出现以下错误(gcc 11.0.1):

<source>:15:22: error: request for member 'operator()' is ambiguous
   15 |     static_assert(o(0) + o('a') == 3);
      |                      ^

<source>:12:9: note: candidates are: 'main()::<lambda(char)>'
   12 |         [](char){ return 2; }
      |         ^

<source>:11:9: note:                 'main()::<lambda(int)>'
   11 |         [](int) { return 1; },
      |         ^

预期的解决方案是将F::operator()和 都G::operator()带入using声明的范围。然而——非常令人惊讶的是——引入一个看似无关的方法operator()也有效:

template <typename F, typename G>
struct overload_two : F, G
{
    overload_two(F f, G g) : F{f}, G{g} { }
    auto operator()(F&) { }  // wtf?
};

Godbolt.org 上的现场示例


更重要的是,clang 13.0.0 似乎在没有using声明或 random 的情况下工作operator()godbolt.org 上的现场示例


我的问题是:

  1. gcc拒绝执行overload_two没有任何operator()using声明是否正确?

  2. gcc 接受overload_twowithoperator()(F&)和 nousing声明的实现是否正确?

  3. clang 在overload_two没有任何operator()using声明的情况下接受实施是否正确?

回答

  1. 是的。名称的名称查找operator()是不明确的,因为它在两个碱基中都找到了名称(请参阅[class.member.lookup]/5.2)。那是一个错误,不要通过GO,不要收集200美元。
  2. 是的。无关的operator()通过隐藏基类成员使名称查找成功。因为您的 lambda 是无捕获的,所以它们可以隐式转换为函数指针,并且这些隐式转换由派生类继承。当您的类可转换为函数指针(或引用)时,重载解析将额外从这些转换生成代理调用函数。这些将被选择用于示例中的调用。
  3. 不。见#1。

以上是通过引入不相关的调用运算符-clangvsgcc奇怪地解决了模棱两可的调用运算符重载的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>