I am writing a sorting library with sorter function objects. One of the main classes, sorter_facade
, is intended to provide some overloads of operator()
to the sorter depending on the overloads that already exist. Here is a simple reduced example of a heap_sorter
object, implementing a heapsort:
struct heap_sorter:
sorter_facade<heap_sorter>
{
using sorter_facade<heap_sorter>::operator();
template<typename Iterator>
auto operator()(Iterator first, Iterator last) const
-> void
{
std::make_heap(first, last);
std::sort_heap(first, last);
}
};
One of the simplest goals of sorter_facade
is to provide an iterable overload to the sorter's operator()
when an overload taking a pair of iterators already exists. Here is a reduced implementation of sorter_facade
, sufficient for the problem at hand:
template<typename Sorter>
struct sorter_facade
{
template<typename Iterable>
auto operator()(Iterable& iterable) const
-> std::enable_if_t<
not has_sort<Sorter, Iterable>,
decltype(std::declval<Sorter&>()(std::begin(iterable), std::end(iterable)))
>
{
return Sorter{}(std::begin(iterable), std::end(iterable));
}
};
In this class, has_sort
is a trait used to detect whether a sorter has an operator()
overload taking an Iterable&
. It is implemented using a hand-rolled version of the detection idiom:
template<typename Sorter, typename Iterable>
using has_sort_t = std::result_of_t<Sorter(Iterable&)>;
template<typename Sorter, typename Iterable>
constexpr bool has_sort = std::experimental::is_detected_v<has_sort_t, Sorter, Iterable>;
Now, to the actual problem: the following main
works well with g++ 5.2:
int main()
{
std::vector<int> vec(3);
heap_sorter{}(vec);
}
However, it fails with clang++ 3.7.0, with the following error message:
main.cpp:87:5: error: no matching function for call to object of type 'heap_sorter' heap_sorter{}(vec); ^~~~~~~~~~~~~ /usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with Iterable = std::vector<int, std::allocator<int> >] using enable_if_t = typename enable_if<_Cond, _Tp>::type; ^ main.cpp:75:10: note: candidate function template not viable: requires 2 arguments, but 1 was provided auto operator()(Iterator first, Iterator last) const ^ 1 error generated.
Apparently, when evaluating the std::enable_if_t
, it seems to consider that Sorter
already has an operator()
able to take an Iterable&
, which probably means that clang++ and g++ do not valuate the "same" Sorter
when checking for the existence of the overload.
For this simple example, removing the std::enable_if_t
makes the whole thing work, but the class sorter_facade
is actually much bigger than that and I need it to resolve ambiguity problems with other overloads of operator()
, so just removing it isn't a solution.
So... what causes the error? Should compilers accept or reject this code? Finally, is there a standard-compatible way to make this work with the latest versions of g++ and clang++?
EDIT: as a side note, I managed to get all the crazy to work with both g++5 and clang++3.8 by adding another layer of black magic to the point I've no idea why it even works anymore at all. While of all the previous questions hold, here is the « workaround » (using C++17 std::void_t
):
tempate<typename Sorter>
struct wrapper:
Sorter
{
#ifdef __clang__
using Sorter::operator();
template<typename Iterable>
auto operator()(Iterable& iterable) const
-> std::enable_if_t<false, std::void_t<Iterable>>
{}
#endif
};
template<typename Sorter>
struct sorter_facade
{
template<typename Iterable>
auto operator()(Iterable& iterable) const
-> std::enable_if_t<
not has_sort<wrapper<Sorter>, Iterable>,
decltype(std::declval<Sorter&>()(std::begin(iterable), std::end(iterable)))
>
{
return Sorter{}(std::begin(iterable), std::end(iterable));
}
};
I guess that it abuses different compiler-specific behaviours in both g++ and clang++ and achieves something that wasn't meant to work, but still... I'm amazed that it works, even in my whole project, which has many more tricky things to handle...
I am pretty sure this is a bug in clang. The return type of sorter_facade<Sorter>::operator()
depends on the template argument Iterator. Still, the compiler seems to decide to SFINAE out before knowing the arguments.
But bug or not, you can work around this by explicitly deferring the calculation of the return type. Here is a version that does not depend on black magic. Works with gcc-5.2 and clang-3.6:
template<typename Sorter, typename Iterable>
struct sort_result
{
using type = decltype(
std::declval<Sorter&>()(
std::begin(std::declval<Iterable&>()),
std::end(std::declval<Iterable&>())));
};
template<typename Sorter, typename Deferred>
using sort_result_t = typename sort_result<Sorter, Deferred>::type;
template<typename Sorter>
struct sorter_facade
{
template <typename Iterable>
auto operator()(Iterable& iterable) const
-> sort_result_t<Sorter, Iterable>
{
return Sorter{}(std::begin(iterable), std::end(iterable));
}
};
struct heap_sorter:
sorter_facade<heap_sorter>
{
using sorter_facade<heap_sorter>::operator();
template<typename Iterator>
auto operator()(Iterator first, Iterator last) const
-> void
{
std::make_heap(first, last);
std::sort_heap(first, last);
}
};
int main()
{
std::vector<int> vec(3);
heap_sorter{}(vec);
}
The trick is: The compiler does not know if you specialize the result_type later on. Therefore it has to wait until you actually use it, before trying to determine the return type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With