将整型数组转换为可变模板参数

4
假设我有一个整型数组,如int arr[N],并且假设arr[i]的取值范围很小(例如1-10)。此外,我还有一个具有通用接口(抽象类)的可变参数模板类。
template <int... A>
class FooImpl : public Foo
{
}

问题是如何实现一个函数:
Foo* getFoo(int arr[N]);

也许更好的选择是:
Foo* getFoo(int* pint, int size);

如何返回模板参数与我的数组相对应的FooImpl?例如,对于arr = {4,2,6,1},我将得到FooImpl<4,2,6,1>


1
如果你想知道如何在运行时提供模板参数(一个编译时的东西)并返回FooImpl,那么是不可能的。你可以使用类似的初始化器,但是运行时数据评估无法产生编译时的实现。 - WhozCraig
我非常确定我能做到。在最坏的情况下,我可以简单地枚举所有大小为1、2等的情况...当大小为1时有10种情况,大小为2时有100种情况,以此类推。请注意,所有这些类都有相同的基类(即接口)Foo,并且请注意我的元素来自一个有限的集合。 - Огњен Шобајић
老实说,我不知道你刚才说了什么,但我坚持我的评论。你不能运行时内容推断出编译时模板参数。如果你认为你可以做类似于int ar[3]; ar[0] = 1; ar[1] = 2; ar[2] = 3;然后以某种方式将ar发送到一个工厂,这将导致一个FooImpl<1,2,3>,我唯一看到的方法是一个静态表格有1000个条目,每个插槽都静态定义为返回特定的FooImpl<a,b,c>但在那一点上它并不是真正的运行时。而且你肯定不会从中继承。 - WhozCraig
好的,这就是我说的。我可以通过硬编码来实现我需要的所有情况。在 size = 3 的情况下,我将有1000 + 100 + 10 = 1110 种不同的情况和相应数量的代码行来覆盖所有这些情况。我的问题是,如何以聪明的方式完成它,使我的代码不至于长达1110行? - Огњен Шобајић
至少看起来我理解了你的意思。我需要考虑一下是否可以使用模板生成器生成模板来实现这个功能。 - WhozCraig
通过明智地使用预处理器,或者更好的是 m4,您可以避免编写 1110 行代码。使用预处理器,您可以将代码量减少到大约 40 行;使用 m4,您可以在不到 20 行的代码中完成,并通过两个预处理时间变量控制实例化的数量。无论哪种方式都很丑陋,但如果您准备实际编写 1110 个模板实例化,那么应该使用所有可用的工具来减少代码。 - cmaster - reinstate monica
2个回答

1
我找到了问题的答案。关键是使用结构体可变模板而不是我最初尝试的函数可变模板。我使用_getFoo_impl结构体和func函数逐个构建元素。
假设元素在[1-5]范围内,大小<=4,那么代码如下:
class Foo
{
};

template <int...A>
class FooImpl : Foo {
};

template<int...As>
struct _getFoo_impl
{
    static Foo* func(int *arr, int sz)
    {
        if (sz == 0)
            return new FooImpl<As...>;

        switch (*arr)
        {
        case 1: return _getFoo_impl<As..., 1>::func(arr + 1, sz - 1);
        case 2: return _getFoo_impl<As..., 2>::func(arr + 1, sz - 1);
        case 3: return _getFoo_impl<As..., 3>::func(arr + 1, sz - 1);
        case 4: return _getFoo_impl<As..., 4>::func(arr + 1, sz - 1);
        case 5: return _getFoo_impl<As..., 5>::func(arr + 1, sz - 1);
        default: throw "element out of range";
        }
    }
};

template<int A1, int A2, int A3, int A4, int A5>
struct _getFoo_impl<A1, A2, A3, A4, A5>
{
    static Foo* func(int*, int sz) {
        std::terminate();
    }
};

Foo* getFoo(int *arr, int size)
{
    return _getFoo_impl<>::func(arr, size);
}

如果你有100个元素呢? - kyb
然后你生成代码,但要检查问题,它明确说“小型域”。 - Огњен Шобајић

0

您不能从类模板中存储或生成一个类型

template <int... N>
class FooImpl;

使用运行时整数作为模板参数。但是,如果以下内容也适用于您的实际问题,您可以存储并查找指向函数的指针。

template <int... N>
R FooFun(A...);

假设函数签名R(A...)对于所有模板参数都是相同的,给定运行时整数作为模板参数。请注意,这里的A...不是一组模板参数;它只是您自己具体函数参数类型的占位符,例如FooFun(int, int)

在我看来,这个表述确实适合您的问题,因为您有一个没有输入参数的工厂函数,返回指向对象FooImpl<N...>的指针,始终被视为Foo*,所以在您的情况下,FooFun看起来会像这样

template <int... N>
Foo* FooFun();

这种转换的一般解决方案基于一个指向函数的查找表,并在我的上一个问题将非constexpr整数值适应为非类型模板参数这个答案中给出(顺便说一下,它现在运行得非常顺畅,我很满意 - 我的实际实现在这里)。
你的情况不同之处在于你需要一个多维查找表。我建议你首先定义一个只有一个整数模板参数的函数“dispatcher”。
template <int OFFSET>
R FooDispatch(A...);

这个表示多维表中的线性偏移量。在这种情况下,前面的解决方案直接适用于FooDispatch。然后,您需要将OFFSET转换为一组多维索引N...,在编译时进行。为此,您还需要在编译时了解表的维度,或者更好地说是它的步长。通过推导出参数N...FooDispatch现在可以调用FooFun<N...>(...)

更准确地说,这种偏移量到索引的转换逻辑与Matlab的函数ind2sub完全相同,只不过它是一个编译时操作。这需要一些工作,所以如果您喜欢这种方法并且需要在最后一步中获得帮助,我很乐意提供协助。在这种情况下,我认为您最好发布一个适当形式的子问题的新问题。

上述所有内容都意味着维度的数量也在编译时已知,因此最终会有一个形如以下的函数:

Foo* getFoo(int arr[N]);

作为接口是可以的,但是

Foo* getFoo(int* pint, int size);

并没有太多意义; 在后一种情况下,您只能进行运行时检查,如果维度不符合,则可能会引发异常。

最后,请注意,在运行时使用超出范围的索引将具有与在普通C数组中使用超出范围的索引相同的效果。


我不太明白你的回答。老实说,我期望看到一段代码,即getFoo()函数的实现。 - Огњен Шобајић

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接