为什么std::hash是一个结构而不是一个函数?

24

标准库使用模板结构实现std::hash,并对不同类型进行专门化。 使用方法如下:

#include <iostream>
#include <functional>

int main()
{
    std::hash<int> hasher;
    std::cout << hasher(1337) << std::endl;

    return 0;
}

我的问题是,这个设计选择的原因是什么。为什么它不能作为一个模板函数实现,并像这样使用:

#include <iostream>
#include <functional>

int main()
{
    std::cout << std::hash<int>(1337) << std::endl;

    return 0;
}

这两个例子完全相同,唯一的区别在于第二个示例中对象未命名。 - user2485710
无序关联容器有一个模板类型参数来指定哈希函数;这允许使用具有状态的哈希对象(例如,使用在哈希函数上异或的特殊值)。获取函数模板特化的类型没有很好的语法。 - dyp
1
@user2485710 第一个例子可以编译通过,第二个则不行。第二个需要写成std::hash<int>()(1337)来使用未命名的临时结构体。 - user743382
2个回答

19

有多个原因可以解释这个选择,每一个都足够好:

  1. 您可以部分特化类模板,但只能完全专门化函数模板(至少目前是这样)。因此,使用类模板的 std::hash<T> 可以为整个相关模板参数组提供替代。请注意,部分重载并不能帮助,因为散列函数需要以某种方式指定为对象,而无法使用重载的函数来完成这一点(除非它们通过对象访问,但这就是不同之处)。
  2. 无序关联容器是使用静态实体(如果特定类型支持也可以动态自定义)进行参数化的,这可以更轻松地使用类模板实现。
  3. 由于用于散列函数的实体是可自定义的,因此选择在类型和函数指针之间。函数指针通常很难内联,而类型的内联成员函数则非常容易内联,对于计算简单哈希等简单函数的性能有很大提升。

3
公平地说,默认哈希可以调用一个函数,然后尝试调用“.hash()”方法来处理不支持的类型,这将允许实现上述所有内容,包括允许ADL哈希(这比强制使用std注入更好),并且允许对象自行进行哈希(如果需要的话)。但是这违反了KISS原则,尽管它与“less”和“begin”的工作方式相匹配。 - Yakk - Adam Nevraumont

5

模板函数不能为类型进行部分特化,而std::hash作为类模板可以对不同的类型进行特化。

通过这种基于模板类的方式,您可以进行一些元编程操作,例如访问返回类型和键类型,如下所示:

std::hash<X>::argument_type
std::hash<X>::result_type

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