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?
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 thanInsert<Pos, Val, Vector<Head, Tail...>>, but theVector<Elems...>is less specialized than theVector<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 intoVector<Head, Tail...>so that theInsert<0, Val, Vector<Elems...>>andInsert<Pos, Val, Vector<Head, Tail...>>are only diff inPos. On the other hand, a specialization is added to process the situation of addingValto emptyVectoratPos=0.And @Bob__ gives another solution based on C++20 by using
requires. Therequiresdescribes the constraints on thePosargument.