You're obviously right that this will not work because the compiler cannot match a template specialization that is based on such a dependent type (e.g., Inner could be a nested typedef, then how would the compiler be able to tell the difference between that type coming from the nested typedef in Outer, versus from elsewhere? It can't, it's impossible to tell).
There are a number of solutions.
First, you could move the Inner class to the outside of the Outer class (and make them friends, if needed). You can also move it into a "detail" namespace or hide it in a number of other ways depending on your context. It is not uncommon for people to avoid such nested "Inner" classes because they can cause a number of problems like this and some older compilers even have problems accepting such nested classes at all. It's generally better practice to just move those nested classes out of the Outer class. In terms of actual code, you do this:
template <typename T>
struct Outer; // forward-decl.
namespace detail {
template <typename T>
struct Outer_Inner {
friend class Outer<T>; // Optional
// ....
};
};
template <typename T>
struct Outer {
typedef detail::Outer_Inner<T> Inner;
friend class detail::Outer_Inner<T>; // Optional
// ...
};
namespace std {
template<typename T>
struct hash< detail::Outer_Inner<T> > {
// ..
};
};
Another solution is to define your own hashing class that you can give to the unordered_set
. Like this:
template <typename T>
struct Outer {
struct Inner {
//..
};
struct InnerHash {
typedef Inner argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& s) const {
return /* some hashing code */;
};
};
// ...
// An example unordered-set member:
std::unordered_set<Inner, InnerHash> m_set;
};
And finally, there is yet another solution I can think of that, like the first solution, has the advantage of specializing the std::hash
class template. However, this solution is a bit convoluted, it involves wrapping your Inner class into an outside class template, like this:
template <typename T>
struct InnerWrapper {
typedef typename Outer<T>::Inner value_type;
value_type data;
};
and then creating the specialization std::hash< InnerWrapper<T> >
. This solution really only has the advantage of being non-intrusive on the existing implementation of the Outer class, but creating an unordered_map
in this case means that the map must contain (directly or indirectly) InnerWrapper objects instead of storing the Inner objects directly. Also, you should notice that this solution can be mixed with the first solution by having some of the functionality of Inner that is more tightly integrated with Outer implemented in a nested class, and having the more "public" functionality of Inner implemented in the outside class, thus avoiding the friendship relationship and allowing tighter Outer-Inner integration, while leaving a clean user-facing class to access Inner's functionalities.
typename Outer<T>::Inner
. But it is a private type. Is that by design? – StaceyClass template partial specialization contains a template parameter that can not be deduced, this partial specialization will never be used
(clang++ 4.3ish) – Winounordered_map
is ok. That code however uses on thehash<typename Outer<T>::Inner>
, so if it does not get instantiated, one has to wonder what hash is used instead. Does theunordered_map
compile and work as expected? Maybe the warning is misleading or bogous. – Crystalloidunordered_map
it doesn't compile – Winotypename Outer<T>::Inner
is when you are specialising it; it could be anything. You will have to moveInner
out ofOuter
and useInner<T>
. – Weeperhash<Inner>
, the same error you would get if you didn't write any specialization at all – WinoInner
which would make the code pretty unreadable. I probably would prefer to explicitly instantiatehash<Outer<T>::Inner
for each concrete typeT
that I am going to use. – WinoT
of the specialization appears in a non-deduced context, this deduction cannot happen. (But I'm not sure how to phrase a coherent argument.) – Caveshash<T>
should behash<T,void>
, so we can run a SFINAE test againstT
to determine if we want to specialize it: that is, however, ugly. I wonder if concept based specializations will solve this issue?template<MyConcept X>struct hash<X>{
? – PrecedentU
used in somehash<U>
has the same type ofOuter<T>::Inner
for a certainT
except trying to compile the templateOuter<>
for all possible types. – Wino