I am trying to do a parameter pack unrolling by dispatching to a class recursively. I'd like to do that from right to left since some of the operations do pre-pending.
template <typename... T>
class Foo;
template <typename T>
class Foo<T> {/* base case implementation*/};
template <typename T, typename R, typename... Rs>
class Foo<T, Rs..., R> {
private:
Foo<T, Rs...> foo_;
}
Unfortunately, the above gets me:
class template partial specialization contains template parameters that cannot be deduced; this partial specialization will never be used
This seems odd to me, I would assume that even though the arguments has switched order, Foo<T, Rs..., R>
should still match the template specialization.
I've looked at some similar questions:
Specifically, C++ template partial specialization: Why cant I match the last type in variadic-template?
However, the highest voted (non-accepted) answer doesn't make sense to me. Sure I understand that the template parameter pack declaration must be the last in the declaration, but I do that for the template specialization.
I'm not sure why the compiler cannot map Foo<T, Rs..., R>
to the initial template declaration Foo<T...>
and enforces the parameter pack declaration order there.
Other answers on that thread offer how to extract the last value, but that still doesn't allow you to do recursive parameter pack unrolling, which is sort of the whole point here. Is it just impossible to unroll a parameter pack from right to left?
Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.
Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.
Pack expansion A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order. template<class...
Here is an utility to instatiate a template with a reverse order of template parameters:
#include <type_traits>
#include <tuple>
template <template <typename...> typename Template, typename ...Arg>
struct RevertHelper;
template <template <typename > typename Template, typename Arg>
struct RevertHelper<Template, Arg>
{
using Result = Template<Arg>;
};
template <template <typename... > typename Template, typename Head, typename ...Tail>
struct RevertHelper<Template, Head, Tail...>
{
private:
template <typename ...XArgs>
using BindToTail = Template<XArgs..., Head>;
public:
using Result = typename RevertHelper<BindToTail, Tail...>::Result;
};
static_assert(std::is_same_v<typename RevertHelper<std::tuple, int, double>::Result, std::tuple<double, int>>, "");
So if you need to instantiate Foo
with template pack Args...
being reversed you can use
typename RevertHelper<Foo, Args...>::Result
To do the parameter pack expansion the way you want, dispatch to the reversed implementation:
namespace internal {
template <typename... T>
class FooHelper;
template <typename T>
class FooHelper<T> {/* base implementation */}
template <typename L, typename R, typename... Rs>
class FooHelper<T> {
private:
Foo<T, Rs...> foo_helper_;
};
}
template <typename... T>
class Foo {
typename RevertHelper<internal::FooHelper, T...>::Result foo_helper_;
};
I'm not sure why the compiler cannot map
Foo<T, Rs..., R>
to the initial template declarationFoo<T...>
and enforces the parameter pack declaration order there.
Because partial ordering is already a really complex algorithm and adding extra complexity to that is fraught with peril. There was a proposal to make this work, which had this example:
template <class A, class... B, class C> void foo(A a, B... b, C c); foo(1, 2, 3, 4); // b is deduced as [2, 3]
Straightforward enough right? Now, what if C
has a default argument? What does this do:
template <class A, class... B, class C=int> void foo(A a, B... b, C c=5);
foo(1, 2, 3, 4);
There are two interpretations of this:
b
is deduced as the pack {2, 3}
and c
is deduced as 4
b
is deduced as the pack {2, 3, 4}
and c
is deduced as 5
Which is intended? Or do we just disallow default arguments after a function parameter pack?
Unfortunately, we have no nice pack indexing mechanism. In the meantime, just use Boost.Mp11:
template <typename... T>
class Foo;
template <typename T>
class Foo<T> {/* base case implementation*/};
template <typename T, typename... Rs>
class Foo<T, Rs...> {
private:
using R = mp_back<Foo>;
mp_pop_back<Foo> foo_;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With