专门为受限制的std::tuple定制std::hash被认为是未定义行为吗?

7

我知道为通用的 std::tuple 专门定制 std::hash 是未定义行为。

但是,如果为特定的 tuple 定制 std::hash 呢?

例如,

namespace std {
  template<>
  struct hash<std::tuple<MyType, float>> {
    size_t operator()(const std::tuple<MyType, float>& t) const {
        // ...
    } 
  };
}

甚至可以使用带有某种 required is_all_same_as_v<Ts…, MyType> C++20 约束的std::tuple<Ts ...>,以确保元组中的所有类型都完全等于MyType

例如,

namespace std {
  template<typename... Ts>
    requires (sizeof...(Ts) > 0 && is_all_same_as_v<Ts..., MyType>)
  struct hash<std::tuple<Ts...>> {
    size_t operator()(const std::tuple<Ts...>& t) const {
        // ...
    } 
  };
}

这仍然被认为是未定义的行为吗?如果是,为什么?

编辑:确保检查参数包的大小至少为1,以避免特化std::hash<std::tuple<>>,这是绝对未定义的行为。


3
可以的。"[namespace.std]/2"规定:除非明确禁止,程序可以向命名空间std添加任何标准库类模板的模板专门化声明,前提是(a)所添加的声明依赖于至少一个程序定义的类型,且(b)该专门化满足原始模板的标准库要求。"hash<std::tuple<MyType, float>>"显然符合任何合理定义下"MyType"的依赖关系(标准似乎没有正式定义这种“依赖于”的关系;可能适用于依赖名称的规则)。 - Igor Tandetnik
2
一开始,我不确定 required is_all_same_as_v<Ts…, MyType> 是否会使模板“依赖于” MyType。很可能是的,只要添加这样的特化不会干扰查找其他不提及 MyType 的特化。 - Igor Tandetnik
1个回答

6

规则在 [namespace.std]/2 中如下:

除非明确禁止,程序可以为任何标准库类模板添加模板特化到命名空间std中,前提是 (a) 添加的声明依赖于至少一个程序定义类型,并且 (b) 特化符合原始模板的标准库要求。

在此,程序定义类型来自[defns.prog.def.type],它指:

非闭包类类型或枚举类型,不属于C++标准库并且未由实现定义,或者是非实现提供的lambda表达式的闭包类型,或者是程序定义特化的实例化

您对 std::tuple<MyType, float>std::hash 特化是正确的,因为它依赖于至少一个程序定义类型 (MyType),并且满足 std::hash 的要求。

你对于std::tuple<Ts...>std::hash的其他特化,只要所有的Ts...都是特定的MyType,也是可以的,原因与之前相同(只要你的限制条件明确排除了std::tuple<>)。那也是程序定义的类型。
你用什么机制来创建或约束专门化并不重要,只要你提供的任何专门化依赖于至少一个程序定义的类型即可。
(请注意,仅提供某些约束是不够的。我的上一个答案版本误读了你的约束条件,仅检查所有类型是否相同。虽然受到了约束,但仍会为像std::tuple<std::string,std::string>这样的类型创建专门化,其中没有程序定义的类型,因此将产生未定义的行为)。

如果是这样,为什么?

这里的问题最终在于一致性。谁被允许专门化什么,何处进行专门化。如果标准库可以提供专门化,您不希望与其冲突。更广泛地说,如果多个库尝试为常见事物定义专门化,则如果每个库仅为其自己的类型提供专门化,将特别有用-否则,如果多个库都尝试提供 std :: hash <std :: tuple <int>>,那对任何人都不会有好处。

这是一个非常好的、非常重要的观点,元组特化应该确保元组中至少有一个元素! - supernun
有人可能会认为,“添加的声明”是一个缺陷:不仅声明必须提到程序定义的类型,而且它必须仅影响涉及其中一个的特化的解释。 - Davis Herring

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