TLDR
I am trying to write a template specialisation for a bunch of derived classes only once in terms of the base class in a CRTP style framework. However, I can't get it to compile.
我正在尝试编写一组派生类的模板专门化,只针对CRTP样式框架中的基类编写一次。但是,我无法对其进行编译。
What I am trying to do
I have a base class and want to produce several flavours of derived classes. The base class names a few methods which would return objects of the type of the derived class. Because of this, I have been using the CRTP pattern to couple the two (C++23's "deducing this" is not an option sadly because at the time of writing most compilers don't support it yet).
我有一个基类,想要生成几种风格的派生类。基类命名了几个将返回派生类类型的对象的方法。正因为如此,我一直在使用CRTP模式将两者结合在一起(遗憾的是,C++23的S不能“推断这一点”,因为在撰写本文时,大多数编译器还不支持它)。
I want to be able to hash these objects (so I can put them in certain STL containers), so I am trying to write a specialisation of std::hash
, but am coming unstuck. I can write a specialisation in terms of a derived class, but can't figure out how to write a general one in terms of the base class.
我希望能够散列这些对象(这样我就可以将它们放在特定的STL容器中),所以我正在尝试编写std::hash的专门化,但我失败了。我可以编写派生类方面的专门化,但不知道如何编写基类方面的通用专门化。
The code
The base and derived classes and the 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 {}
};
How I want to use these
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
}
The template specialisations
// 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; }
};
What the compiler says
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;
更多回答
Even if you have class D : public B {}
; still std::hash<D>
and std::hash<B>
are two distinct, unrelated classes: writing std::hash<D>{}
doesn't magically instantiate std::hash<B>
. Similarly, there's no relationship between std::hash<Derived_A>
and std::hash<Base<Derived_A>>
即使您有类D:PUBLIC B{};std::hash和std::hash仍然是两个不同的、不相关的类:编写std::hash{}不会神奇地实例化std::hash。同样,std::hash和std::hash之间也没有关系
优秀答案推荐
What you're looking for is this:
你要找的是这个:
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; }
};
You match for any T
what extends Base<T>
.
可以匹配任何扩展Base
的T。
The reason why this works and not the previous one is because of the relationship between templates and inheritance - or lack thereof.
这种方法之所以有效,而不是前一种方法,是因为模板和继承之间的关系--或者说缺乏这种关系。
An instantiated type has no relationship with any other instantiated types. std::vector<int>
is a completely different and unrelated type compared to std::vector<float>
.
实例化类型与任何其他实例化类型没有关系。与std::VECTOR
相比,STD::VECTOR
是一个完全不同且无关的类型。
The same is true for types that have relationship between each other. So for example, std::hash<A>
and std::hash<Base<A>>
are also completely unrelated types. When a container instantiate std::hash<A>
, it will instantiate that type and only that type precisely. As your previous code did only provide hashes for std::hash<Base<A>>
, the type std::hash<A>
had to fallback to the non specialized one which caused the compilation error.
对于彼此有关系的类型也是如此。因此,例如,std::hash和std::hash
也是完全无关的类型。当容器实例化std::hash
时,它将准确地实例化该类型且仅实例化该类型。由于您之前的代码确实只为std::hash
提供了散列,因此std::hash
类型必须后退到导致编译错误的非专门化类型。
Bonus, you should change the dynamic_cast<Self const*>(this)
to static_cast<Self const*>
since you know at compile time that the conversion is valid.
另外,您应该将DYNAMIC_CAST
(This)更改为STATIC_CAST
,因为您在编译时知道转换是有效的。
If you want to be extra sure you can add such private method in base:
如果您想要特别确定,可以在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);
}
更多回答
Thanks. Are you able to provide a bit of explanation / know-how about why the template with the requires ...
gets picked up, and what was going wrong with my attempt, so I can better understand what's going on/wrong under the hood.
谢谢。你能提供一点关于为什么模板需要的解释/诀窍吗?被选中,以及我的尝试出了什么问题,这样我就可以更好地了解引擎盖下面发生了什么/错误。
@oliversm let me edit
@oleverm让我编辑
我是一名优秀的程序员,十分优秀!