CRTP 根据 std::hash 的基类为派生类提供专业化

CRTP provide specialisation for derived classes in terms of base class for std::hash

提问人:oliversm 提问时间:9/11/2023 更新时间:9/11/2023 访问量:68

问:

顶级域名

我正在尝试根据 CRTP 样式框架中的基类为一堆派生类编写一次模板专用化。但是,我无法让它编译。

我想做什么

我有一个基类,想要生成几种派生类。基类命名了一些方法,这些方法将返回派生类类型的对象。正因为如此,我一直在使用 CRTP 模式将两者耦合(可悲的是,C++23 的“推导”不是一个选项,因为在撰写本文时,大多数编译器还不支持它)。

我希望能够对这些对象进行哈希处理(这样我就可以将它们放在某些 STL 容器中),所以我正在尝试编写一个 的专业化,但我没有卡住。我可以根据派生类编写专业化,但无法弄清楚如何根据基类编写通用专业化。std::hash

代码

基类和派生类以及 CRTP

#include <vector>

template<typename Self>
class Base {
protected:
    using my_type = int;
    std::vector<my_type> bar; // <- I will be doing something special to hash this...

public:
    virtual void baz(void) const = 0;

    Self foo(void) const {
        Self result = *dynamic_cast<const Self *>(this);
        // do something with the private bar variable.
        return result;
    };
};

class Derived_A final : public Base<Derived_A> {
public:
    void baz(void) const override {}
};

class Derived_B : public Base<Derived_B> {
public:
    void baz(void) const override {}
};

我想如何使用这些

int main() {
    Derived_A d_a;
    Derived_B d_b;
    auto d_a_ = d_a.foo();
    d_a.baz();
    std::hash<Derived_B>{}(d_b);
    std::hash<Derived_A>{}(d_a); // <-- This doesn't compile
}

模板专业化

// Works. 
template<>
struct std::hash<Derived_B> {
    std::size_t operator()(const auto &shape) const noexcept { return 0; }
};

// Not working. 
template<typename Self>
struct std::hash<Base<Self>> {
    std::size_t operator()(const Base<Self> &shape) const noexcept { return 0; }
};

编译器说什么

error: call to deleted constructor of '__enum_hash<Derived_A>'
    std::hash<Derived_A>{}(d_a);
                         ^
/usr/local/opt/llvm/bin/../include/c++/v1/__functional/hash.h:643:5: note: '__enum_hash' has been explicitly marked deleted here
    __enum_hash() = delete;
C++ 哈希 模板专用化 CRTP

评论

2赞 Igor Tandetnik 9/11/2023
即使你有 ;still 和 是两个截然不同的、不相关的类:写作不会神奇地实例化。同样,和 之间也没有关系class D : public B {}std::hash<D>std::hash<B>std::hash<D>{}std::hash<B>std::hash<Derived_A>std::hash<Base<Derived_A>>

答:

1赞 Guillaume Racicot 9/11/2023 #1

你要找的是这个:

template<typename T> requires std::is_base_of_v<Base<T>, T>
struct std::hash<T> {
    std::size_t operator()(T const& shape) const noexcept { return 0; }
};

你匹配任何扩展的东西。TBase<T>

之所以这样做而不是前一个,是因为模板和继承之间的关系 - 或缺乏关系。

实例化类型与任何其他实例化类型没有关系。 与 相比,是一种完全不同且不相关的类型。std::vector<int>std::vector<float>

对于彼此之间有关系的类型也是如此。例如,也是完全不相关的类型。当容器实例化时,它将精确地实例化该类型,并且只实例化该类型。由于您之前的代码只提供了 的哈希值,因此该类型必须回退到导致编译错误的非专用类型。std::hash<A>std::hash<Base<A>>std::hash<A>std::hash<Base<A>>std::hash<A>


奖励,您应该更改为 ,因为您在编译时知道转换是有效的。dynamic_cast<Self const*>(this)static_cast<Self const*>

如果您想更加确定可以在 base 中添加这样的私有方法:

auto self() const& -> Self const& {
    static_assert(std::is_base_of_v<Base<Self>, Self>);
    return *static_cast<Self const&>(this);
}

auto self() & -> Self& {
    static_assert(std::is_base_of_v<Base<Self>, Self>);
    return *static_cast<Self&>(this);
}

评论

0赞 oliversm 9/11/2023
谢谢。您是否能够提供一些解释/诀窍,说明为什么带有模板的模板会被选中,以及我的尝试出了什么问题,这样我就可以更好地了解引擎盖下发生了什么/错误。requires ...
0赞 Guillaume Racicot 9/11/2023
@oliversm让我编辑