为依赖类型专门化std::hash<T>

5
我已经定义了这个模板类结构:
template<typename T> struct Outer {
    struct Inner { /* ...some stuff... */ };
};

我希望能将Inner对象放入unordered_map中(实际上,并不是直接放入,而是将其容器放入其中,因此在unordered_map的模板参数中直接指定哈希对象的方法并不好),因此我想为这些项目专门定制hash类。
然而,这种方法行不通,因为编译器无法将Outer<T>::Inner与在实例化hash时指定的类型匹配:
namespace std {
    template<typename T> struct hash<typename Outer<T>::Inner > {
        size_t operator ()( typename Outer<T>::Inner const & obj )
        { /* ...some stuff... */ }
    };
};

有没有人知道这个问题的解决方案?


@juanchopanza 不好意思,我写错了。它是公共的,并且已经有typename了。 - pqnet
2
你遇到了什么错误? - juanchopanza
@ArneMertz 编译器错误并不是什么有趣的事情。它抱怨找不到hash <Inner>的特化,这与您如果根本没有编写任何特化时会遇到的错误相同。 - pqnet
1
Relevant. - Baum mit Augen
1
这就是为什么 hash<T> 应该是 hash<T,void> 的原因,这样我们就可以对 T 进行 SFINAE 测试以确定是否要对其进行特化:然而,这种方法很丑陋。我想知道基于概念的特化是否能解决这个问题? template<MyConcept X>struct hash<X>{? - Yakk - Adam Nevraumont
显示剩余9条评论
1个回答

5

显然,这种做法行不通,因为编译器无法匹配基于这样一个依赖类型的模板特化(例如,Inner可以是一个嵌套的typedef,那么编译器如何区分该类型来自Outer中的嵌套typedef,还是来自其他地方?它无法区分,这是不可能的)。

有许多解决方案。

首先,您可以将Inner类移到Outer类的外部(如果需要,可以使它们成为友元)。您也可以将它移动到“详细信息”命名空间中或以其他方式隐藏它,具体取决于您的上下文。人们通常避免使用这些嵌套的“Inner”类,因为它们可能会引起许多问题,比如一些旧的编译器甚至无法接受这些嵌套类。通常最好的做法是将这些嵌套类移出Outer类。在实际代码中,您可以这样做:

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> > {
    // ..
  };
};

另一种解决方法是定义自己的哈希类,然后将其提供给unordered_set。代码示例如下:
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;

};

最后,我想到了另一种解决方案,与第一个方案相似,具有专门化std::hash类模板的优点。但是,这种解决方案有些复杂,需要将您的内部类包装在一个外部类模板中,如下所示:

template <typename T>
struct InnerWrapper {
  typedef typename Outer<T>::Inner value_type;
  value_type data;
};

然后创建专门化的std::hash< InnerWrapper<T> >。这种解决方案的优点仅在于不会对现有的Outer类实现产生影响,但在此情况下创建一个unordered_map意味着该映射必须包含(直接或间接地)InnerWrapper对象而不是直接存储Inner对象。此外,您应该注意到,通过在嵌套类中实现与Outer更紧密集成的Inner某些功能,并在外部类中实现更“公共”的Inner功能,可以将此解决方案与第一种解决方案混合使用,从而避免友元关系并允许更紧密的Outer-Inner集成,同时保留一个干净的用户界面类来访问Inner的功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接