gcc vs clang - ambiguous overload when using `make_overload` variadic lambda inheritance [duplicate]

隐身守侯 提交于 2019-12-07 16:39:44

问题


Time for another round of clang vs gcc. Live example on godbolt.org.


Test 0: overloaded callable object

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 compiles and run.
  • clang++ 3.5 (and later versions) compiles and run.

Test 1: overloaded callable object, generated via lambda inheritance

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 does not compile.

    • error: request for member 'operator()' is ambiguous

  • clang++ 3.5 (and later versions) compiles and run.


What compiler is correct here?


回答1:


I can give you a workaround.

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

here, we inherit from a bunch of distinct parent types, each with an operator(). These do not (at least in gcc) overload the way you want.

To fix this, we inherit linearly and carry () down via 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)...) 
   {}
};

now we replace overload_set as follows:

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)...)
  {}
};

and both gcc and clang should like it.

The linear inheritance is sub-optimal: a balanced binary tree is better, but would take more work. (basically, you take a pack Xs... and you split it into Xs_front... and Xs_back... using careful TMP, put those in a types<...> pack, transcribe those to your two parents, and do the using blah::operator() thing). This is because the compiler has a limit on recursive template instantiations and inheritance depth that tends to be more shallow than the limit of total template instantiations and inheritance "volume".


In c++17 we don't have to do this linear inheritance:

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

because they added a new spot you can do ... expansion.



来源:https://stackoverflow.com/questions/33303888/gcc-vs-clang-ambiguous-overload-when-using-make-overload-variadic-lambda-inh

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!