I'm trying to port some code written for GCC (8.2) to be compilable by Clang:
#include <tuple>
struct Q{};
using TUP = std::tuple<Q>;
template<typename Fn>
inline
void feh(Fn&, const std::tuple<>*)
{}
template<typename Fn, typename H>
inline
void feh(Fn& fn, const std::tuple<H>*)
{
fn(H{});
}
template<typename Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
fn(H{});
using Rest = const std::tuple<R...>*;
feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
}
template<typename Tuple, typename Fn>
inline
void fe(Fn& fn, const Tuple * tpl = nullptr)
{
feh(fn, tpl);
}
int main()
{
auto r = [] (Q const&) {};
TUP tup;
fe<TUP>(r, &tup);
}
GCC 8.2 (and 12.1) compiles the code just fine. However, Clang 11.0.0 (and 14.0.0) complains that the call from fe
to feh
is ambiguous between void feh(Fn& fn, const std::tuple<H>*) [with Fn = (lambda at <source>:38:14), H = Q]
and void feh(Fn& fn, const std::tuple<H, R...>*) [with Fn = (lambda at <source>:38:14), H = Q, R = <>]
.
https://godbolt.org/z/5E9M6a5c6
Which compiler is right?
How can I write this code so both compilers accept it?
Both if constexpr
and fold expressions would work in C 17, but this is a library header included by many projects, and not all of them are compiled with C 17. I need a solution which works in C 11.
CodePudding user response:
Which compiler is right?
I think that clang is wrong in rejecting the code because the first overload candidate feh(Fn& fn, const std::tuple<H>*)
should be preferred over the other candidate feh(Fn& fn, const std::tuple<H, R...>*)
since the former is more specialized than the latter.
In other words, the version without the pack is considered more specialized and hence should be preferred if it matches the call.
This is because, basically(roughly) for one function template to be considered more specialized than the other, the latter should be able to accept all the template arguments that the former can accept but not vice-versa.
Now, in your given example the overload feh(Fn& fn, const std::tuple<H, R...>*)
can accept(or work with) all template arguments which the former feh(Fn& fn, const std::tuple<H>*)
can accept but the reverse is not true. Hence the former is more specialized than the latter. For more technical details of this process, refer to What is the partial ordering procedure in template deduction or from function template overloading which states:
In case of a tie, if one function template has a trailing parameter pack and the other does not, the one with the omitted parameter is considered to be more specialized than the one with the empty parameter pack.
(emphasis mine)
CodePudding user response:
I'm unsure which compiler that is correct, but...clang
is correct because both functions matches equally good.
A C 11 solution could be to just add the requirement that the Rest
part must contain at least one type and that is easily done by just adding R1
. That would mean that the rest of your code could be left unchanged:
template<typename Fn, typename H, typename R1, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R1, R...>*)
{
fn(H{});
using Rest = const std::tuple<R1, R...>*;
feh<Fn, R1, R...>(fn, static_cast<Rest>(nullptr));
}
A C 17 solution would be to remove the other feh
overloads and use a fold expression:
template <typename Fn, typename... H>
inline void feh(Fn& fn, const std::tuple<H...>*) {
(..., fn(H{}));
}
This is a unary left fold over the comma operator which "unfolded" becomes:
(((fn(H1{}), fn(H2{})), ...), fn(Hn{}))
CodePudding user response:
By far the simplest solution is an if constexpr
:
template<typename Fn, typename H, typename... R>
inline
void feh(Fn& fn, const std::tuple<H, R...>*)
{
fn(H{});
if constexpr (sizeof...(R) > 0) {
using Rest = const std::tuple<R...>*;
feh<Fn, R...>(fn, static_cast<Rest>(nullptr));
}
}
and just remove the problematic overload.