Today I tried to implement radix sort. The function must have two variables: begin iterator and end iterator, and can have third: some function that must return the integer type to sort by. By default it must be the identity function.
My tries look like (sorry, code looks very long and dirty, but it's just a try):
template<class ForwardIt>
void radix_sort(
ForwardIt first,
ForwardIt last,
std::function<auto(typename std::iterator_traits<ForwardIt>::value_type)> get_value =
[](const typename std::iterator_traits<ForwardIt>::value_type& x){ return x; }) {
// ...
}
The returning type of get_value, of course, will be known in compilation time.
Usage should be:
std::vector<std::pair<uint32_t, std::string>> vec;
// ...
radix_sort(vec.begin(), vec.end(), [](const std::pair<uint32_t, std::string>& x){ return x.first; })
Or:
std::vector<uint32_t> vec;
// ...
radix_sort(vec.begin(), vec.end());
It doesn't even compile and I don't know how to solve the problem. How to do it? Simple example:
#include <bits/stdc++.h>
template<class ForwardIt>
void radix_sort(
ForwardIt first,
ForwardIt last,
std::function<auto(typename std::iterator_traits<ForwardIt>::value_type)> get_value =
[](const typename std::iterator_traits<ForwardIt>::value_type& x){ return x; }) {
// ...
}
int main()
{
std::vector<std::pair<uint32_t, std::string>> vec(10);
radix_sort(vec.begin(), vec.end());
}
Compiler output:
source_file.cpp:17:37: error: no matching function for call to ‘radix_sort(std::vector<unsigned int>::iterator, std::vector<unsigned int>::iterator)’
radix_sort(vec.begin(), vec.end());
^
source_file.cpp:6:6: note: candidate: template<class ForwardIt, class auto:1> void radix_sort(ForwardIt, ForwardIt, std::function<auto:1(typename std::iterator_traits<_Iter>::value_type)>)
void radix_sort(
^
source_file.cpp:6:6: note: template argument deduction/substitution failed:
source_file.cpp:17:37: note: couldn't deduce template parameter ‘auto:1’
radix_sort(vec.begin(), vec.end());
The easy way to fix this is to not have a default function and instead have two overloads. This lets you get rid of using a std::function
, which is expensive, at the cost of writing a couple lines of boiler plate code. If you use
template<class ForwardIt, class Func>
void radix_sort(ForwardIt first, ForwardIt last, Func get_value) {
// ...
}
template<class ForwardIt>
void radix_sort(ForwardIt first, ForwardIt last) {
radix_sort(first, last, [](const typename std::iterator_traits<ForwardIt>::value_type& x){ return x; });
}
the you get the default "identity" with no function, and you get the exact function object otherwise if one is provided.
For the benefit of future users,
I would like to point out that C++20 introduces the class std::identity
,
which aids in solving the problem.
With its help,
the code can be rewritten as:
template <typename For, typename F = std::identity>
void radix_sort(For first, For end, F f = {})
{
/* ... */
}
And it is very easy to implement a standard-conforming one yourself if you don't have C++20, like this:
struct identity {
template <class T>
constexpr T&& operator()(T&& t) const noexcept
{
return std::forward<T>(t);
}
using is_transparent = void;
};
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