4

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?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • MSVS2015 will compile the second example but the intellisense does not like it. – NathanOliver Oct 23 '15 at 13:51
  • This is a dupe of...something. Anyway, GCC is right. – T.C. Oct 23 '15 at 13:54
  • 1
    @T.C: why is GCC right? ...shouldn't the code generated by `make_overload` be equivalent to `struct Trad { ... }`? Is there any workaround to make `make_overload` work? – Vittorio Romeo Oct 23 '15 at 13:56
  • @VittorioRomeo Try `struct Trad0 { auto operator()(int){return 1;}};` etc, then `struct Trad:Trad0, Trad1, Trad2 {` etc. Notice when you call `operator()` on *this* version of `Trad`, the same ambiguity occurs. Basically, overload rules don't work the way you want them to when the overloads are picked between distinct bases provided methods, where no `using` occurs to move them to the same scope: instead, you get "ambiguous". – Yakk - Adam Nevraumont Oct 23 '15 at 13:58
  • @t.c. I have seen this before here to. I didn't remember which one was right, just that to get it to work consistently you have to jump through certain hoops. – Yakk - Adam Nevraumont Oct 23 '15 at 14:00

1 Answers1

2

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 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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524