I am writing a sort library with sorter function objects. One of the main classes sorter_facade designed to provide some operator() overloads for the sorter, depending on existing overloads. Here is a simple example of a heap_sorter object that implements 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 repeated overloading to the operator() sorter when an overload that takes a pair of iterators already exists. The following is a reduced implementation of sorter_facade , sufficient for the problem in question:
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 the attribute used to determine if the sorter has operator() overloaded with Iterable& . It is implemented using a manual 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 real problem: the following main works well with g ++ 5.2:
int main() { std::vector<int> vec(3); heap_sorter{}(vec); }
However, it does not work 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 std::enable_if_t it seems that Sorter already has an operator() capable of accepting Iterable& , which probably means that clang ++ and g ++ do not evaluate the โsameโ Sorter when checking for overload.
For this simple example, removing std::enable_if_t makes it all work, but the sorter_facade class is actually much larger than this, and I need it to resolve the ambiguity problems with other operator() overloads, so just removing this is not 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, adding another layer of black magic to the point, I don't know why this works anymore. Despite all the previous questions, here is a โ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 assume that it abuses various compiler-specific behaviors in both g ++ and clang ++ and achieves something that is not intended to work, but still ... I am amazed that it works even in my entire project, which is a lot of more complicated things that need to be processed ...