How to unroll a parameter pack from right to left
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}
andc
is deduced as4
b
is deduced as the pack{2, 3, 4}
andc
is deduced as5
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_;
};
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_;
};
Pattern matching in C++ template patterns is intentionally simplified for sake of simplicity of algorithm and understanding.
Take a look at hypothetical algorithm if this could be possible:
- Get some declaration: using
X = Foo<int, char, bool, double>
; - Compiler checks specializations: first one is Foo - it's dropped.
- Compiler checks specializations: second one is your
Foo<T, Rs..., R>
T
isint
, we're fine.R
's can be emtpy, let's try to skip it.R
ischar
, but we're at the end of specialization parameters, let's get back to 2.R
's is charR
isbool
, but we're at the end of specialization parameters, let's get back to 2.R
's ischar
,bool
R
isdouble
, we're fine, select this one
But this is only one scenario: another one would be to eat all parameters to the end and cut off one by one in order to try to match it. This can be problematic, because such template specialization would be inherently ambiguous with another possible specialization that doesn't seem to be an ambiguity here:
template<typename T, typename S>
class Foo<T, S> {};