gcc vs clang - 使用`make_overload` variadic lambda继承时的模糊重载

时间:2015-10-23 13:44:49

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

另一轮 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 (及更高版本)编译并运行。

这里的编译器是正确的?

1 个答案:

答案 0 :(得分:2)

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

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<...>包,将这些转录给你的双亲,做using blah::operator()事。这是因为编译器对递归模板实例化和继承深度的限制往往比总模板实例化和继承“卷”的限制更浅。

中,我们不必执行此线性继承:

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

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