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 declaration Foo<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_;
};

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:

  1. Get some declaration: using X = Foo<int, char, bool, double>;
  2. Compiler checks specializations: first one is Foo - it's dropped.
  3. Compiler checks specializations: second one is your Foo<T, Rs..., R>
    1. T is int, we're fine.
    2. R's can be emtpy, let's try to skip it.
    3. R is char, but we're at the end of specialization parameters, let's get back to 2.
    4. R's is char
    5. R is bool, but we're at the end of specialization parameters, let's get back to 2.
    6. R's is char, bool
    7. R is double, 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> {};

Tags:

C++

C++11