【问题标题】:gcc vs clang - ambiguous overload when using `make_overload` variadic lambda inheritance [duplicate]gcc vs clang-使用`make_overload`可变参数lambda继承时不明确的重载[重复]
【发布时间】:2016-01-23 01:57:17
【问题描述】:

又是一轮clang vs gcc的时间了。 Live example on godbolt.org.


测试0:重载的可调用对象

struct Trad
{
    auto operator()(int)    { return 1; }
    auto operator()(float)  { return 2; }
    auto operator()(double) { return 3; }
};

int main()
{
    assert(Trad{}(1) == 1);
    assert(Trad{}(1.f) == 2);
    assert(Trad{}(1.0) == 3);
}
  • g++ 5.2 编译并运行。
  • clang++ 3.5 (和更高版本) 编译并运行。

测试 1:重载的可调用对象,通过 lambda 继承生成

template <typename... TFs>
struct overload_set : TFs...
{
    overload_set(TFs... fs) : TFs(fs)... {}
};

template <typename... TFs>
auto overload(TFs&&... fs)
{
    return overload_set<TFs...>{fs...};
}

int main()
{
    auto func = overload 
    (
        [](int)    { return 1; }, 
        [](float)  { return 2; }, 
        [](double) { return 3; }
    );

    assert(func(1) == 1);
    assert(func(1.f) == 2);
    assert(func(1.0) == 3);
}
  • g++ 5.2 无法编译。

    • 错误:对成员“operator()”的请求不明确

  • clang++ 3.5 (和更高版本) 编译并运行。


这里什么编译器是正确的?

【问题讨论】:

  • MSVS2015 将编译第二个示例,但智能感知不喜欢它。
  • 这是骗人的……某事。无论如何,GCC 是对的。
  • @T.C:为什么 GCC 是对的? ...make_overload生成的代码不应该等同于struct Trad { ... }吗?是否有任何解决方法可以使make_overload 工作?
  • @VittorioRomeo 尝试struct Trad0 { auto operator()(int){return 1;}}; 等,然后尝试struct Trad:Trad0, Trad1, Trad2 { 等。请注意,当您在 版本的Trad 上调用operator() 时,会出现同样的歧义。基本上,当在不同的基提供的方法之间选择重载时,重载规则不会按照您希望的方式工作,其中没有发生using 将它们移动到相同的范围:相反,您会得到“模棱两可”。
  • @t.c.我以前在这里看到过这个。我不记得哪一个是正确的,只是为了让它始终如一地工作,你必须跳过某些环节。

标签: c++ gcc lambda clang c++14


【解决方案1】:

我可以给你一个解决方法。

template <typename... TFs>
struct overload_set : TFs...
{
  overload_set(TFs... fs) : TFs(fs)... {}
};

在这里,我们继承了一堆不同的父类型,每个父类型都有一个operator()。这些不会(至少在 gcc 中)以您想要的方式超载。

为了解决这个问题,我们线性继承并通过using 向下传递()

template<class...Ts>
struct inherit_linearly;
template<>
struct inherit_linearly<>{};
template<class T0, class...Ts>
struct inherit_linearly<T0, Ts...>:
  T0, inherit_linearly<Ts...>
{
   using T0::operator();
   using inherit_linearly<Ts...>::operator();
   template<class A0, class...As>
   inherit_linearly( A0&&a0, As&&...as ):
     T0(std::forward<A0>(a0)),
     inherit_linearly<Ts>(std::forward<As>(as)...) 
   {}
};

现在我们替换overload_set如下:

template <typename... TFs>
struct overload_set : inherit_linearly<TFs...>
{
  using inherit_linearly<TFs...>::operator();
  overload_set(TFs... fs) :
    inherit_linearly<TFs...>(std::forward<TFs>(fs)...)
  {}
};

gcc 和 clang 都应该喜欢它。

线性继承是次优的:平衡的二叉树更好,但需要更多的工作。 (基本上,你拿一个包Xs...,然后使用小心的 TMP 将它分成Xs_front...Xs_back...,把它们放在一个types&lt;...&gt; 包中,把它们转录给你的两个父母,然后做using blah::operator() 的事情) .这是因为编译器对递归模板实例化和继承深度的限制往往比总模板实例化和继承“量”的限制更浅。


中,我们不必进行这种线性继承:

template <typename... TFs>
struct overload_set : TFs...
{
  using TFs::operator()...;
  overload_set(TFs... fs) : TFs(fs)... {}
};

因为他们添加了一个新位置,您可以进行 ... 扩展。

【讨论】:

  • 谢谢。也找到了another workaround
  • 为了完整起见,您应该在 inherit_linearly 的显式特化中声明 operator(),否则代码几乎总是会失败。
  • @joh 是的,或者添加一个 1 arg 规范。随时编辑,我在打电话。
猜你喜欢
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 2016-11-18
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
相关资源
最近更新 更多