是什么导致了非类型模板参数的部分专用化中的不同行为?

What caused the different behaviors in partial specialization of non-type template argument?

提问人:LChao 提问时间:10/7/2023 最后编辑:LChao 更新时间:10/13/2023 访问量:97

问:

我正在尝试完成模板元编程练习文本。但是,当我尝试部分专用化非类型模板参数时,我遇到了不同的行为。

我定义了一个编译时向量。

template <int... Nums>
struct Vector {};

然后,我定义一个关于“Vector”的“Get”操作,就可以编译代码了。

template <size_t Idx, typename Vec>
struct Get;

template <int Head, int... Tail>
struct Get<0, Vector<Head, Tail...>> // partial specialize the "Idx"
{
    static constexpr int value = Head;
};

template <size_t Idx, int Head, int... Tail>
struct Get<Idx, Vector<Head, Tail...>>
{
    static_assert(Idx > 0, "Out of Vector bound");
    static constexpr int value = Get<Idx - 1, Vector<Tail...>>::value;
};

但是,当我尝试定义如下所示的“插入”操作时,代码编译在 VS2022 中失败,错误代码为“C2752 多个部分专用化匹配”。

template <size_t Pos, int Val, typename Vec>
struct Insert;

template <size_t Pos, int Val, typename Vec>
using InsertT = typename Insert<Pos, Val, Vec>::type;

template <int Val, int... Elems>
struct Insert<0, Val, Vector<Elems...>> // partial specialize the "Pos"
{
    using type = Vector<Val, Elems...>;
};

template <size_t Pos, int Val, int Head, int... Tail>
struct Insert<Pos, Val, Vector<Head, Tail...>>
{
    using type = PrependT<Head, InsertT<Pos - 1, Val, Vector<Tail...>>>; // push an element in the front of the vector
};

更新:

#include <type_traits>

template <int... Nums>
struct Vector {};

template <int, typename>
struct Prepend;

template <int Ele, typename Vec>
using PrependT = typename Prepend<Ele, Vec>::type;

template <int Ele, int... Nums>
struct Prepend<Ele, Vector<Nums...>>
{
    using type = Vector<Ele, Nums...>;
};

template <size_t Idx, typename Vec>
struct Get;

template <int Head, int... Tail>
struct Get<0, Vector<Head, Tail...>> // partial specialize the "Idx"
{
    static constexpr int value = Head;
};

template <size_t Idx, int Head, int... Tail>
struct Get<Idx, Vector<Head, Tail...>>
{
    static_assert(Idx > 0, "Out of Vector bound");
    static constexpr int value = Get<Idx - 1, Vector<Tail...>>::value;
};

template <size_t Pos, int Val, typename Vec>
struct Insert;

template <size_t Pos, int Val, typename Vec>
using InsertT = typename Insert<Pos, Val, Vec>::type;

template <int Val, int... Elems>
struct Insert<0, Val, Vector<Elems...>> // partial specialize the "Pos"
{
    using type = Vector<Val, Elems...>;
};

template <size_t Pos, int Val, int Head, int... Tail>
struct Insert<Pos, Val, Vector<Head, Tail...>>
{
    using type = PrependT<Head, InsertT<Pos - 1, Val, Vector<Tail...>>>; // push an element in the front of the vector
};

int main()
{
    // test Vector
    static_assert(std::is_same_v<Vector<1, 2>, Vector<1, 2>>);

    // test Prepend
    static_assert(std::is_same_v<Prepend<1, Vector<2, 3>>::type, Vector<1, 2, 3>>);

    // test Get
    static_assert(Get<0, Vector<0, 1, 2>>::value == 0);
    static_assert(Get<1, Vector<0, 1, 2>>::value == 1);
    static_assert(Get<2, Vector<0, 1, 2>>::value == 2);

    // test Insert
    static_assert(std::is_same_v<Insert<0, 3, Vector<4, 5, 6>>::type, Vector<3, 4, 5, 6>>);
    static_assert(std::is_same_v<Insert<1, 3, Vector<4, 5, 6>>::type, Vector<4, 3, 5, 6>>);
    static_assert(std::is_same_v<Insert<2, 3, Vector<4, 5, 6>>::type, Vector<4, 5, 3, 6>>);
    static_assert(std::is_same_v<Insert<3, 3, Vector<4, 5, 6>>::type, Vector<4, 5, 6, 3>>);
}

在我看来,两种操作的部分专业化是相同的。为什么一个编译成功,而另一个编译失败?

C++ 模板元编程 部分专用化

评论

1赞 Bob__ 10/7/2023
如何定义?不应与 ?PrependTIdxPos
0赞 Paul Sanders 10/7/2023
代码有点奇怪,因为它似乎实际上没有做任何事情。不应该是函数模板吗?GetInsert
0赞 Igor Tandetnik 10/7/2023
制作它(注意模板参数列表中的 no)。如所写,无法推断。这与 的第一个专业化相同,其中您省略了template <size_t Val, int... Elems> struct Insert<0, Val, Vector<Elems...>>PosPosGetIdx
0赞 LChao 10/7/2023
@Bob__,我将完整的代码放在“update:”下面,包括“PrependT”的定义。
0赞 LChao 10/7/2023
@Igor Tandetnik 我已经修改了代码,但它仍然不起作用。所有代码都放在“更新”下方。

答:

1赞 LChao 10/13/2023 #1

正如 @Igor Tandetnik 所提到的,源码不起作用的原因是,在第一个参数上比 更专业,但 不如 。因此,任何一个专业都不比另一个专业更专业Insert<0, Val, Vector<Elems...>>Insert<Pos, Val, Vector<Head, Tail...>>Vector<Elems...>Vector<Head, Tail...>

基于上述事实,Igor Tandetnik 提出了以下解决方案。此解决方案有两个主要修改。一方面,将 更改为 ,以便 和 仅在 中比较。另一方面,添加了专用化以处理添加到空 的情况。Vector<Elems...>Vector<Head, Tail...>Insert<0, Val, Vector<Elems...>>Insert<Pos, Val, Vector<Head, Tail...>>PosValVectorPos=0

template <size_t Pos, int Val, typename Vec>
struct Insert;

template <size_t Pos, int Val, typename Vec>
using InsertT = typename Insert<Pos, Val, Vec>::type;

template <int Val>
struct Insert<0, Val, Vector<>> // Insert element at 0 into an empty vector.
{
    using type = Vector<Val>;
};

template <int Val, int Head, int... Tail>
struct Insert<0, Val, Vector<Head, Tail...>>  // Insert element at 0 into non-empty vector.
{
    using type = Vector<Val, Head, Tail...>;
};

template <size_t Pos, int Val, int Head, int... Tail>
struct Insert<Pos, Val, Vector<Head, Tail...>>
{
    using type = PrependT<Head, InsertT<Pos - 1, Val, Vector<Tail...>>>; // push an element in the front of the vector
};

@Bob__ 通过使用 给出了另一种基于 C++20 的解决方案。描述参数的约束。requiresrequiresPos

template <size_t Pos, int Val, typename Vec>
struct Insert;

template <size_t Pos, int Val, typename Vec>
using InsertT = typename Insert<Pos, Val, Vec>::type;

template <int Val, int... Elems>
struct Insert<0ull, Val, Vector<Elems...>> // partial specialize the "Pos"
{
    using type = Vector<Val, Elems...>;
};

template <size_t Pos, int Val, int Head, int... Tail>
  requires (Pos > 0)                                       // <-----
struct Insert<Pos, Val, Vector<Head, Tail...>>
{
    using type = PrependT<Head, InsertT<Pos - 1, Val, Vector<Tail...>>>; // push an element in the front of the vector
};