如何从右到左展开参数包

How to unroll a parameter pack from right to left

本文关键字:参数 从右到左      更新时间:2023-10-16

我正在尝试通过递归调度到类来展开参数包。我想从右到左这样做,因为某些操作确实预先挂起。

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

不幸的是,以上让我:

类模板部分专用化包含无法推导的模板参数; 这种部分专用化永远不会被使用

这对我来说似乎很奇怪,我认为即使参数已经切换了顺序,Foo<T, Rs..., R>仍然应该与模板专业化相匹配。

我看过一些类似的问题:

具体来说,C++模板部分专用化:为什么我无法匹配可变参数模板中的最后一个类型?

但是,投票最高(未接受)的答案对我来说没有意义。当然,我知道模板参数包声明必须是声明中的最后一个,但我这样做是为了模板专用化。

我不确定为什么编译器无法Foo<T, Rs..., R>映射到初始模板声明Foo<T...>并在那里强制执行参数包声明顺序。

该线程上的其他答案提供了如何提取最后一个值,但这仍然不允许你进行递归参数包展开,这是这里的重点。只是不可能从右到左展开参数包吗?

下面是一个实用程序,用于使用模板参数的相反顺序来设置模板:

#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>>, "");

因此,如果您需要使用模板包实例化FooArgs...被反转,您可以使用

typename RevertHelper<Foo, Args...>::Result

要按照您想要的方式执行参数包扩展,请调度到反向实现:

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

我不确定为什么编译器无法Foo<T, Rs..., R>映射到初始模板声明Foo<T...>并在那里强制执行参数包声明顺序。

因为部分排序已经是一个非常复杂的算法,增加额外的复杂性充满了危险。有一个提案要做这项工作,其中有这样一个例子:

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]

足够简单吧?现在,如果C有一个默认参数怎么办?这是做什么的:

template <class A, class... B, class C=int> void foo(A a, B... b, C c=5);
foo(1, 2, 3, 4);

对此有两种解释:

  • b被推算为包{2, 3}c被推算为4
  • b被推算为包{2, 3, 4}c被推算为5

这是有意的?还是我们只是不允许函数参数包后的默认参数?


不幸的是,我们没有很好的包索引机制。同时,只需使用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_;
};

为了简化算法和理解,有意简化了C++模板模式中的模式匹配。

如果可能的话,看看假设的算法:

  1. 获取一些声明:使用X = Foo<int, char, bool, double>;
  2. 编译器检查专业化:第一个是Foo-它被删除了。
  3. 编译器检查专业:第二个是你的Foo<T, Rs..., R>
    1. Tint,我们很好。
    2. R的可能很糟糕,让我们尝试跳过它。
    3. Rchar,但我们在专用化参数的末尾,让我们回到 2。
    4. R的字符
    5. Rbool,但我们在专用化参数的末尾,让我们回到 2。
    6. Rcharbool
    7. Rdouble,我们很好,选择这个

但这只是一种情况:另一种情况是将所有参数吃到最后,然后一个接一个地切断以尝试匹配它。这可能会有问题,因为这种模板专用化本质上是模棱两可的,而另一种可能的专用化在这里似乎不是歧义:

template<typename T, typename S>
class Foo<T, S> {};