gcc vs clang - ambiguous overload when using `make

2019-05-10 09:28发布

问题:

This question already has an answer here:

  • Overloaded lambdas in C++ and differences between clang and gcc 2 answers

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.