I want to implement some generic algorithms and I have a number of ideas how specialized algorithms could be implemented depending on certain traits of entities the algorithm is used with. However, it seems likely that I didn't come up with all special traits and I'd like to implement the generic version such that they could work with another specialized version.
For example, consider distance(begin, end)
(yes, I know it is in the standad library; however, it is nice and simple and can be used to demonstrate my problem). A general version could look like this (I'm using std::ptrdiff_t
instead of std::iterator_traits<It>::difference_type
as another simplification):
template <typename It> auto distance(It it, It end) -> std::ptrdiff_t { std::ptrdiff_t size{}; while (it != end) { ++it; ++size; } return size; }
Of course, if the iterator type is a random access iterator, it is much better to implement the algorithm using the difference between the two iterators. Naively just adding
template <typename It> auto distance(It begin, It end) -> typename std::enable_if<is_random_access_v<It>, std::ptrdiff_t>::type { return end - begin; }
doesn't quite work: both implementation are equally good matches for random access iterators, i.e., the compiler considers them to be ambiguous. The easy approach to deal with the situation is to change the general implementation to be applicable only for non random access iterators. That is, the SFINAE choices are made such that they are mutually exclusive while also covering the entire space.
Unfortunately, the set of implementations is still closed: without changing the signature of, at least, one of the implementations I can't add another implementation in case I have another idea for a generic implementation taking advantage of special properties. For example, if I want to add special processing for segmented ranges (idea: when the underlying sequence consists of segments as is, e.g., the case for std::deque<...>
or std::istreambuf_iterator<cT>
, process segments individually) it would be necessary to change the general implementation to be applicable only when the sequences isn't a random access and it isn't a segmented sequence. Of course, if I control the implementation that can be done. A user wouldn't be able to extend the set of generic implementations.
I am aware that the functions can be overloaded for special iterator types. However, that would require that every time an iterator with special capabilities is added it would need to implement the respective functions. The goal is to enable adding generic implementations which areimprovements in case the entities they are used with expose additional facilities. It is similar to the different iterator categories, although the properties are orthogonal to the iterator categories.
Thus, my question is:
Answer: Yes, we can overload a generic methods in C# as we overload a normal method in a class.
Overloads keyword Overloading in general refers to creating multiple procedures with the same name that accept different argument types in a given class. You can use the Overloads keyword to declare a property or a method with the same name but with a different argument list.
What Does Overloading Mean? Overloading refers to the ability to use a single identifier to define multiple methods of a class that differ in their input and output parameters. Overloaded methods are generally used when they conceptually execute the same task but with a slightly different set of parameters.
In TypeScript, function overloading, or method overloading, is the ability to create multiple methods with the same name and same return type, but a different number of parameters or different parameter types. So essentially, method overloading is allowed when – Function name is same.
One approach is a ranking-based overload mechanism. Assign each overload a rank and let overload resolution do the rest.
These are the helper traits:
template <unsigned i> struct rank : rank<i - 1> {}; template <> struct rank<0> {}; using call_ranked = rank<256>;
And this is an example usage:
template <typename It> auto distance_ranked(rank<0>, It it, It end) -> std::size_t { std::size_t size{}; while (it != end) { ++it; ++size; } return size; } template <typename It> auto distance_ranked(rank<1>, It begin, It end) -> typename std::enable_if<is_random_access_v<It>, std::size_t>::type { return end - begin; } // Delegating function template: template <typename... Args> auto distance(Args&&... args) -> decltype(distance_ranked(call_ranked(), std::forward<Args>(args)...)) { return distance_ranked(call_ranked(), std::forward<Args>(args)...); }
Demo.
A rank with a higher number is more prioritized than one with a lower number. I.e. rank<1>
causes the second overload to be selected over the first (rank<0>
) if the matches would otherwise be identical.
If you wanted to add a segment-based implementation, use that as the condition for enable_if
. Presumably segmented ranges and random-access ranges would be mutually exclusive, but if they aren't, assign the random-access one a higher priority. The general guideline could be: The more efficient an implementation is, the higher its rank.
Using this method, other implementations shouldn't be affected when introducing a new one. One has to make sure though that any two categories with non-empty intersections (that aren't covered by a category with higher rank) have a different rank - which constitutes a noticeable disadvantage.
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