所有子类的模板专门化

发布于 2024-12-07 10:52:15 字数 1278 浏览 0 评论 0原文

我想定义一个适用于给定基类的所有子类的 C++ 模板专业化。这可能吗?

特别是,我想对 STL 的 hash<> 执行此操作。散列<>被定义为一个空的参数化模板,以及一系列针对特定类型的专业化:

template<class _Key>
  struct hash { };

template<>
  struct hash<char>
  {
    size_t
    operator()(char __x) const
    { return __x; }
  };

template<>
  struct hash<int>
  {
    size_t
    operator()(int __x) const
    { return __x; }
  };
...

我想定义这样的东西:

template<class Base>
  struct hash {
    size_t operator()(const Base& b) const {
      return b.my_hash();
    }
  };

class Sub : public Base {
  public:
    size_t my_hash() const { ... }
};

并能够像这样使用它:

hash_multiset<Sub> set_of_sub;
set_of_sub.insert(sub);

但是,我的哈希模板与 STL 中的通用模板冲突。有没有一种方法(也许使用特征)来定义适用于给定基类的所有子类的模板专业化(无需修改STL定义)?

请注意,我知道只要需要此哈希专业化,我就可以使用一些额外的模板参数来完成此操作,但如果可能的话,我想避免这种情况:

template<>
  struct hash<Base> {
    size_t operator()(const Base& b) const {
      return b.my_hash();
    }
  };

....

// similar specialization of equal_to is needed here... I'm glossing over that...
hash_multiset<Sub, hash<Base>, equal_to<Base> > set_of_sub;
set_of_sub.insert(sub);

I would like to define a C++ template specialization that applies to all subclasses of a given base class. Is this possible?

In particular, I'd like to do this for STL's hash<>. hash<> is defined as an empty parametrized template, and a family of specializations for specific types:

template<class _Key>
  struct hash { };

template<>
  struct hash<char>
  {
    size_t
    operator()(char __x) const
    { return __x; }
  };

template<>
  struct hash<int>
  {
    size_t
    operator()(int __x) const
    { return __x; }
  };
...

I would like to define something like this:

template<class Base>
  struct hash {
    size_t operator()(const Base& b) const {
      return b.my_hash();
    }
  };

class Sub : public Base {
  public:
    size_t my_hash() const { ... }
};

and be able to use it like this:

hash_multiset<Sub> set_of_sub;
set_of_sub.insert(sub);

However, my hash template conflicts with the generic one from STL. Is there a way (perhaps using traits) to define a template specialization that applies to all subclasses of a given base class (without modifying the STL definitions)?

Note that I know I can do this with some extra template parameters whenever this hash specialization is needed, but I'd like to avoid this if possible:

template<>
  struct hash<Base> {
    size_t operator()(const Base& b) const {
      return b.my_hash();
    }
  };

....

// similar specialization of equal_to is needed here... I'm glossing over that...
hash_multiset<Sub, hash<Base>, equal_to<Base> > set_of_sub;
set_of_sub.insert(sub);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

撩人痒 2024-12-14 10:52:15

C++ 11 开始,您可以将 SFINAE 与标准库 enable_ifis_base_of 来解决问题。

Since C++ 11 you can use SFINAE together with standard library enable_if and is_base_of to solve the problem.

转身以后 2024-12-14 10:52:15

C++20 使更清晰的解决方案成为可能 - 基本上等同于enable_if,它甚至(可选)与 CRTP 一起使用

#include <concepts>
#include <functional>
#include <unordered_set> // just for demo in main()

template <class T>
class Base {};

class Derived final : public Base<Derived> {};

template<class T>
requires std::derived_from<T, Base<T>>
struct std::hash<T> {
  // constexpr is optional
  constexpr size_t operator() (const T& value) const noexcept {
    return 0xDEADBEEF; // FIXME: do something better :)
  }
};

int main() {
  // If operator() weren't constexpr, this couldn't be a *static* assert
  static_assert(std::hash<Derived>()(Derived {}) == 0xDEADBEEF);
  std::unordered_set<Derived> usageTest;
  return 0;
}

C++20 makes a cleaner solution possible - basically equivalent to enable_if, which even (optionally) works with CRTP

#include <concepts>
#include <functional>
#include <unordered_set> // just for demo in main()

template <class T>
class Base {};

class Derived final : public Base<Derived> {};

template<class T>
requires std::derived_from<T, Base<T>>
struct std::hash<T> {
  // constexpr is optional
  constexpr size_t operator() (const T& value) const noexcept {
    return 0xDEADBEEF; // FIXME: do something better :)
  }
};

int main() {
  // If operator() weren't constexpr, this couldn't be a *static* assert
  static_assert(std::hash<Derived>()(Derived {}) == 0xDEADBEEF);
  std::unordered_set<Derived> usageTest;
  return 0;
}
北恋 2024-12-14 10:52:15

解决方案是使用 SFINAE 根据类继承结构来决定是否允许您的专业化。在 Boost 中,您可以使用 enable_ifis_base_of 来实现这一点。

The solution is to use SFINAE to decide whether or not to allow your specialisation depending on the class inheritance structure. In Boost you can use enable_if and is_base_of to implement this.

忆梦 2024-12-14 10:52:15

这是我能做的最好的事情:

template<>
  struct hash<Sub> : hash<Base> {
  };

不过,我有点担心我不必将 operator() 设为虚拟。

This was the best I could do:

template<>
  struct hash<Sub> : hash<Base> {
  };

I'm a little worried that I didn't have to make operator() virtual, though.

零度℉ 2024-12-14 10:52:15

我认为这是不可能的,因为基于比仅匹配类型更复杂的东西来进行模板专业化的方法是 C++ SFINAE,它需要第二个(虚拟)模板参数。不幸的是,std::hash 只接受一个模板参数,并且不允许使用两个模板参数创建另一版本的 std::hash 。因此,如果您对 Jayen 的解决方案不满意,您可以可以创建您自己的 hash 类型:

#include <iostream>
#include <type_traits>

using namespace std;

class ParentClass {};
class ChildClass : public ParentClass {};

// SFINAE, if T is not a child of ParentClass, substitution will fail
// You can create specializations for the general case, for another base classes, etc.
template<typename T, typename=typename enable_if<is_base_of<ParentClass, T>::value, T>::type>
struct your_hash
{
    size_t operator()(const T& value)
    {
        return 42;
    }
};

int main()
{
  ParentClass pc;
  ChildClass cc;
  cout<<your_hash<ParentClass>()(pc)<<"\n";
  cout<<your_hash<ChildClass>()(cc)<<"\n";
}

I don't think it is possible, because the way to do template specialization based on something more complex than just matching the type is C++ SFINAE, which requires second (dummy) template argument. Unfortunatelly, std::hash takes only one template argument and it is not allowed to create another version of std::hash with two template arguments. Therefore, the if you aren't satisfied with Jayen's solution, you can create your own hash type:

#include <iostream>
#include <type_traits>

using namespace std;

class ParentClass {};
class ChildClass : public ParentClass {};

// SFINAE, if T is not a child of ParentClass, substitution will fail
// You can create specializations for the general case, for another base classes, etc.
template<typename T, typename=typename enable_if<is_base_of<ParentClass, T>::value, T>::type>
struct your_hash
{
    size_t operator()(const T& value)
    {
        return 42;
    }
};

int main()
{
  ParentClass pc;
  ChildClass cc;
  cout<<your_hash<ParentClass>()(pc)<<"\n";
  cout<<your_hash<ChildClass>()(cc)<<"\n";
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文