c++ 非类型模板形参的实参类型是否应该与形参类型匹配?

5lhxktic  于 2023-06-07  发布在  其他
关注(0)|答案(3)|浏览(174)

我在示例化一个std::array<int,3>std::unordered_set,发现下面的代码无法通过编译:

#include <iostream>
#include <unordered_set>
#include <array>

namespace std {
    template <class T>
    inline void hash_combine(size_t& seed, const T& v) {
        hash<T> hasher;
        seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }

    template <typename T, int N>
    struct hash<array<T, N>>
    {
        size_t operator()(array<T, N> arr) const noexcept {
            size_t hs = 0;
            for (auto iter = arr.begin(); iter != arr.end(); iter++) {
                hash_combine(hs, *iter);
            }
            return hs;
        }
    };
}

int main(int argc, char** argv) {
    std::unordered_set<std::array<int, 3>> arrset;
    std::cout << arrset.size() << std::endl;
    return 0;
}

错误消息告诉我未检测到特殊化hash<array<T, N>>。经过一番努力,我发现这是由于非类型模板参数N的类型与参数(size_t)的类型不匹配造成的。但是编译器不应该自动将int N转换为size_t N吗?由于std::array<int, true>的使用也通过了编译,正如我在g9.4上测试的那样。C标准中有没有特别提到这种情况?

qzlgjiam

qzlgjiam1#

std::hash是一个红色的鲱鱼,因为我把@Jason的答案中描述的正式UB解释为w.r.t.关于OP的问题:
但是编译器不应该自动将int N转换为size_t N吗?
一个最小的例子:

#include <cstddef>

template<std::size_t N>
struct A {};

template<typename T>
struct S { S() = delete; };

template<int N>
struct S<A<N>> {};  // #1

S<A<4>> s{};  // error ("use of deleted function")
              // picks non-defined primary template

temp.class.spec.match(https://timsong-cpp.github.io/cppwp/n4868/temp.class.spec.match)描述了相关规则[emphasismine]:

/1当类模板用于需要类示例化的上下文中时,需要确定示例化是使用主模板还是部分专门化生成。[...]
/2部分专门化匹配给定的实际模板参数列表如果部分专门化的模板参数可以从实际模板参数列表推导出,[...]

从上面的#1,实际的模板参数列表是A<4>,从中我们可以推导出A<std::size_t{4}>类型。但是,部分专门化的模板参数列表是A<int{N}>,它与实际的模板参数列表不匹配。事实上,部分特化#1将永远不会被使用(给定当前示例),因为我们无法生成匹配它的模板参数列表。

vi4fp9gy

vi4fp9gy2#

您还需要将散列函数添加到模板参数中。这里是你的代码的修订版本(也有一些小的反馈)。

#include <iostream>
#include <unordered_set>
#include <array>

namespace std
{
    namespace details
    {
        template <class type_t>
        inline void hash_combine(size_t& seed, const type_t& v)
        {
            auto hash = std::hash<type_t>()(v);
            seed ^= hash + 0x9e3779b9ul + (seed << 6) + (seed >> 2); // initialize magic number as std::size_t too 
        }
    }

    template <typename type_t, std::size_t N>
    struct hash<std::array<type_t, N>>
    {
        size_t operator()(const std::array<type_t, N>& arr) const noexcept // pass by const ref to avoid copies
        {
            size_t hs{ 0ul };

            for (const auto& value : arr) // use range based for loop
            {
                details::hash_combine(hs, value);
            }
            return hs;
        }
    };

}

int main()
{
    std::unordered_set<std::array<int,3>> arrset;

    arrset.insert({ 1,2 });
    std::cout << arrset.size() << std::endl;
    return 0;
}
nkoocmlb

nkoocmlb3#

std::hash专门化为std::array<int, N>会导致未定义行为
从[namespace.std]:

除非明确禁止,否则程序可以将任何标准库类模板的模板特化添加到命名空间std,前提是

(a)添加的声明依赖于至少一个程序定义的类型
(b)该专门化满足原始模板的标准库要求。
(强调我的)
但是在您的示例中,至少有一个程序定义类型的第一个要求没有得到满足。来自[defns.prog.def.type]:
非闭包类类型或枚举类型,它不是C++标准库的一部分,也不是由实现定义的,或者非实现提供的lambda表达式的闭包类型,或者程序定义的专门化的示例化。
一种可能的解决方案是使用lambda。

相关问题