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

Ambiguous call operator overload weirdly resolved by introducing unrelated call operator - clang vs gcc

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

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; },
      |         ^

预期的解决方案是使用 using 声明将 F::operator()G::operator() 纳入范围。然而 - 非常令人惊讶 - 引入一个看似无关的 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?
};

live example on godbolt.org


更重要的是,clang 13.0.0 似乎在没有 using 声明或随机 operator() 的情况下工作:live example on godbolt.org.


我的问题是:

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

  2. gcc 接受带有 operator()(F&) 且没有 using 声明的 overload_two 的实现是否正确?

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

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