Member Function Visibility in the CRTP Class

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

+5
source share
1 answer

I am sure this is a bug in clang. The return type sorter_facade<Sorter>::operator() depends on the Iterator template argument. However, the compiler seems to have decided SFINAE before knowing the arguments.

But error or not, you can get 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); } 

Trick: the compiler does not know if later you specialize in result_type. Therefore, it must wait until you use it before trying to determine the type of return.

+1
source

Source: https://habr.com/ru/post/1235728/


All Articles