假设我有一个核心库,我试图在其中为我的核心类添加
std::hash
的显式专业化,包括 Foo
类(以及许多其他我也想专门化 std::hash
的类)。
Foo.h
struct Foo
{
...
size_t _id;
};
KeyHashing.h,与 Foo 位于同一个库中
#include "Foo.h"
#include <unordered_map>
namespace std
{
template <>
struct hash<Foo>
{
size_t operator()(const Foo& t) const noexcept; // Link error, unresolved external when used in client code
};
// extern template struct hash<Foo>; // error C2039 '()': is not a member of 'std::hash<Foo>'
}
担心在我的标头中添加模板定义,这些模板定义将在它们包含的翻译单元中进行编译,并可能增加我的总 exe 大小和编译时间,我希望
operator()
的实现位于 KeyHashing.cpp 中(在核心库中)并且看起来像这样。
KeyHashing.cpp
#include "Foo.h"
// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
template <typename T, typename... Rest>
void HashCombine(size_t& seed, const T& v, Rest... rest)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(HashCombine(seed, rest), ...);
}
namespace std {
size_t hash<Foo>::operator()(const Foo& t) const noexcept // error C2039 when used with extern template struct hash<Foo> in header.
{
std::size_t ret = 0;
HashCombine(ret, t._id);
return ret;
}
}
在我的客户端代码(核心库之外)中,我将
std::unordered_map
与我的内部核心类无缝地用作键。
那么2个问题:
更新
上面的代码没有链接,有错误
11>MyClass.obj : error LNK2019: unresolved external symbol "public: unsigned __int64 __cdecl std::hash<struct Foo>::operator()(struct Foo const &)const " (??R?$hash@UFoo@@@std@@QEBA_KAEBUFoo@@@Z) referenced in function "public: unsigned __int64 __cdecl std::_Uhash_compare<struct Foo,struct std::hash<struct Foo>,struct std::equal_to<struct Foo> >::operator()<struct Foo>(struct Foo const &)const " (??$?RUFoo@@@?$_Uhash_compare@UFoo@@U?$hash@UFoo@@@std@@U?$equal_to@UFoo@@@3@@std@@QEBA_KAEBUFoo@@@Z)
正如评论中提到的,exe 大小可能会或可能不会受到 ODR 的影响,但编译时间与任何其他指标一样相关,所以我在这里缺少任何语法吗?
您不能使类模板的实现脱离线,也不能使(完整)专业化的实现脱离线。将专业化委托给一个函数(非模板),然后您可以离线实现该函数。像这样的东西(为了简洁而省略):
KeyHashing.h:
size_t HashFoo(const Foo&) noexcept;
namespace std
{
template <>
struct hash<Foo>
{
size_t operator()(const Foo& t) const noexcept
{ return HashFoo(t); }
};
}
KeyHashing.cpp:
size_t HashFoo(const Foo& t) noexcept
{
// your implementation goes here
}
好吧,事实证明我的特殊问题是缺少我在核心库中定义的operator()
__declspec(dllexport)
。因此,虽然 j6t 的答案可以工作,但事实证明我能够有一个脱离线的专业化实现。