Is it possible to make return type of a function depends on some condition about type in template of a class?
I have a custom hash map, named MyHashMap.
It has a proper begin(), and end() function that return iterator.
template <class K, class T> class MyHashMap{
MyIterator<K,T> begin() { ..... }
MyIterator<K,T> end() { ...... }
//.... some function ....
}
And the iterator has a neat operator*(), operator++(), etc
template <class K, class T> class MyIterator{
std::pair<K,T> operator*(){ ..... }
//.... some function ....
}
Now I can use MyHashMap in place of the std::unordered_map, so good.
Can I also extend this class to replace std::hashset too? How?
Specifically, I want to replace std::hashset and std::unordered_map with this class directly.
Both data structure (map & hashset) seems to be very similar (both code & logic), stuff them in one place can increase maintainability.
The problem is that there are many hundreds times call Hash set like this:-
std::hashset<X> hashset; //old code, will be deleted
MyHashMap<X, MyHashMap_DUMMY > hashset; // new code
for(auto x: hashset ){
x.doSomething(); //# old code, compile error, but I don't want to change this
}
I can't change the return signature of hash map either because there are some codes that use it as a real map, not set:-
for(auto xy: hashmap ){ //HashMap<X,Y>
x.first.doSomething(); //# I don't want to change this line too
}
Note: Some lines (#) should not be changed. The reasons are :-
They appear in many places.
Changing them also make code dirty.
In future I may want to replace MyHashMap/MyHashSet back to std::unordered_map/std::hashset later.
If I don't modify #-line, there is little work to be done to change back (High modularity & maintainability).
Create a MyHashSet to encapsulate HashMap. Its disadvantage is that I will have another layer of abstraction, have two classes, more bugs and less maintainability.
I wish there is a trick that can exploit / manipulate template to detect that T is MyHashMap_DUMMY.
In other words,
C++11 and C++14 are allowed.
You can use tag dispatching to switch on T
at compile time:
template <class K, class T> struct MyIterator
{
decltype(auto) operator*()
{
return indirection_hlp(std::is_same<T, MyHashMap_DUMMY>{});
}
std::pair<K, T>& indirection_hlp(std::false_type)
{
// ...
}
K& indirection_hlp(std::true_type)
{
// ...
}
// other stuff
};
As written, this requires C++14 (because of decltype(auto) operator*()
), but the same thing can be implemented in C++11, just with a bit more typing.
I'm not sure if reusing MyHashMap
like that is a good idea in terms of efficiency and even reliability (you may have to do this kind of dispatching in several places), but if it's really what you need, this is one reasonably clean solution that doesn't introduce any runtime overhead by itself.
Note that map / set keys are effectively const
in the standard library, so the return types from the indirection operator should really be std::pair<const K, T>&
and const K&
.
Also note that your for loops use auto x
, which will deduce a non-reference type, and so will make a copy, which may be what you want for a set, but probably not for a map (it will copy the std::pair
).
For C++11, it looks something like this:
template <class K, class T> struct MyIterator
{
std::pair<K, T>& indirection_hlp(std::false_type)
{
// ...
}
K& indirection_hlp(std::true_type)
{
// ...
}
auto operator*() -> decltype(indirection_hlp(std::is_same<T, MyHashMap_DUMMY>{}))
{
return indirection_hlp(std::is_same<T, MyHashMap_DUMMY>{});
}
// other stuff
};
Note that indirection_hlp
needs to be declared before operator*
in this case, because it appears outside of the function body, in a place where name lookup only finds previous declarations (it doesn't look in the whole class definition).