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);
}
What compiler is correct here?
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.