I have the following code:
template<typename T, typename U>
struct combine;
template<template<typename...> typename Tpl, typename... Ts, typename... Us>
struct combine< Tpl<Ts...>, Tpl<Us...> >
{
using type = Tpl<Ts..., Us...>;
};
template<size_t Ind, size_t Curr, typename Tpl>
struct pack_upto_impl;
// SPECIALIZATION 1
template<size_t Matched, template<typename...> typename Tpl, typename... Ts>
struct pack_upto_impl<Matched, Matched, Tpl<Ts...> >
{
using type = Tpl<>;
};
// SPECIALIZATION 2
template<size_t Ind, size_t Curr, template<typename...> typename Tpl, typename T, typename... Ts>
struct pack_upto_impl<Ind, Curr, Tpl<T, Ts...> >
{
using remaining_type = typename pack_upto_impl<Ind, Curr 1, Tpl<Ts...>>::type;
using type = typename combine<Tpl<T>, remaining_type>::type;
};
template<size_t Ind, typename Tpl>
using pack_upto = pack_upto_impl<Ind, 0, Tpl >;
What I want this to do is something like...
using T = tuple<int, double, short, float>;
pack_upto<0, T> var1; // this is tuple<>
pack_upto<1, T> var2; // this is tuple<int>
pack_upto<2, T> var3; // this is tuple<int, double>
...
When I try to do this, I get an error about ambiguous template specialization - when the first two template parameters of pack_upto_impl
are the same, the compiler doesn't get the hint that I want SPECIALIZATION 1 rather than SPECIALIZATION 2.
What's the most elegant way of making this work?
CodePudding user response:
First of all, some typos:
- The
, bool
in your definition ofremaining_type
needs to be removed. - You probably wanted to write
using pack_upto = pack_upto_impl<Ind, 0, Tpl >::type;
. (Before C 20, you also needtypename
here.)
The core issue here is that you want specialization 1 to be considered "more specialized" than specialization 2 so that if a set of template arguments could match either specialization, specialization 1 is chosen. As you have currently written them, specialization 1 is not more specialized than specialization 2.
In order for specialization 1 to be more specialized than specialization 2, it must be the case that you could supply arbitrary values of the template arguments for specialization 1 and have the result match specialization 2 (i.e., the template arguments of specialization 2 could be successfully deduced from anything you could instantiate from specialization 1). At present that condition is not met, because if Ts...
is empty in specialization 1, it won't match specialization 2, which only accepts Tpl<T, Ts...>
(i.e. Tpl
must have at least 1 argument, not 0).
We can fix this by adding an extra specialization; call it specialization 3:
template<size_t Matched, template<typename...> typename Tpl, typename T, typename... Ts>
struct pack_upto_impl<Matched, Matched, Tpl<T, Ts...> >
{
using type = Tpl<>;
};
So in this case, when we have a nonempty argument list to Tpl
, this specialization will be chosen because it is more specialized than specialization 2. (When the argument list is empty, only specialization 1 matches in the first place, and there's no ambiguity.)
See the complete example here on Godbolt.