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

102 Views Asked by At

I'm trying to complete a template metaprogramming exercisetext. However, I meet different behaviors when I' trying to partially specialize an non-type template argument.

I define a compile time vector.

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

Then, I define a "Get" operation about the "Vector", and the code could be compiled.

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;
};

However, When I trying to define the "Insert" operation like below, the code compilation failed in VS2022 and the error code is "C2752 more than one partial specialization matches".

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
};

update:

#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>>);
}

In my opinion, the partial specialization in both operations is the same. Why does one compile successfully while the other fails?

1

There are 1 best solutions below

0
LChao On

As mentioned by @Igor Tandetnik, the reason why the origin code doesn't work is Insert<0, Val, Vector<Elems...>> is more specialized on the first argument than Insert<Pos, Val, Vector<Head, Tail...>>, but the Vector<Elems...> is less specialized than the Vector<Head, Tail...>. Thus, neither specialization is more specialized than the other.

Based on the above facts, Igor Tandetnik proposes the following solution. There are two main modifications to this solution.On the one hand, the Vector<Elems...> is changed into Vector<Head, Tail...> so that the Insert<0, Val, Vector<Elems...>> and Insert<Pos, Val, Vector<Head, Tail...>> are only diff in Pos. On the other hand, a specialization is added to process the situation of adding Val to empty Vector at Pos=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
};

And @Bob__ gives another solution based on C++20 by using requires. The requires describes the constraints on the Pos argument.

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
};