4

Say I have an int array like int arr[N] and say arr[i] are from a tiny domain (e.g. 1-10). Say I also have a variadic templated class with a common interface (abstract class)

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

The question is how do I implement a function:

Foo* getFoo(int arr[N]);

or maybe better:

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

which would return FooImpl with template arguments corresponding my array? For example for arr = {4,2,6,1} I would get FooImpl<4,2,6,1>

iavr
  • 7,547
  • 1
  • 18
  • 53
  • 1
    If you're asking how you can return a `FooImpl` with template arguments (a *compile-time* thing) provided *at runtime*, you can't. You *can* us a similar initializer for both, but runtime-data evaluation cannot produce compile-time fulfillments. – WhozCraig Mar 23 '14 at 10:45
  • I am pretty sure I can. In the worst case I can simply enumerate all the cases with size = 1, 2, etc... There are 10 cases for size = 1, 100 cases for size = 2 and so on. Note that all these classes have the same base class (i.e. interface) Foo and notice that my elements are from a limited set. – Огњен Шобајић Mar 23 '14 at 10:51
  • I honestly have no idea what you just said, but I stand by my comment. You *cannot* deduce compile-time template arguments from *run-time* content. If you think you can do something like `int ar[3]; ar[0] = 1; ar[1] = 2; ar[2] = 3;` then somehow send `ar` to a factory that will result in a FooImpl<1,2,3> the only way I see it happening is a static table of 1000 entries, each slot statically defined to return a specific `FooImpl` But at that point it isn't really run-time. and you're certainly not inheriting from it. – WhozCraig Mar 23 '14 at 10:57
  • Ok, that's what I said. I can do it by hard-coding all the cases I need. Up to size = 3 I would have 1000+100+10=1110 different cases and that many lines of code to cover all these cases. My question is how do I do it in a smart way making my code not 1110 lines of code long? – Огњен Шобајић Mar 23 '14 at 11:04
  • Well at least it looks like I 8did understand you then. I'd have to think about it for a bit to see if it were possible with a generator template of templates. – WhozCraig Mar 23 '14 at 11:09
  • You can avoid writing 1110 lines of code by making judicious use of the preprocessor, or better in this case, of `m4`. With the preprocessor, you can get the amount of code down to roughly 40 lines; with `m4`, you can do it in less 20 lines, and control the number of instanciations by two preprocessing time variables. Either way is ugly, but if you are prepared to actually write out 1110 template instanciations, you should use all tools available to reduce that code. – cmaster - reinstate monica Mar 23 '14 at 12:10

2 Answers2

1

I found the answer to my question. The trick is in using struct variadic templates instead of function variadic templates which I initially tried with. I use _getFoo_impl struct with func function which builds up element by element.

Let's assume the elements are in range [1-5] and the size <= 4 and then the code looks as following:

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);
}
0

You cannot store or generate a type from a class template

template <int... N>
class FooImpl;

with run-time integers as template arguments. However, if the following might also fit your actual problem, you could store and then look up a pointer-to-function

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

given run-time integers as template arguments, assuming the function signature R(A...) is the same for all template arguments. Note that A... is not a pack of template parameters here; it's just a placeholder for the types of your own concrete function parameters, e.g. FooFun(int, int).

It seems to me that this formulation indeed fits your problem because you have a factory function with no input parameters that returns a pointer to object FooImpl<N...> always seen as Foo*, so in your case FooFun would look like

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

A general solution for this kind of conversion is based on a look-up table of pointers-to-function and is given in this answer of my previous question adapting a non-constexpr integral value to a non-type template parameter (which by the way works very smoothly now and I'm very happy with it - my actual implementation is here).

The difference in your case is that you need a multi-dimensional look-up table. I suggest that you first define a function "dispatcher" with only one integer template argument

template <int OFFSET>
R FooDispatch(A...);

which represents the linear offset in the multi-dimensional table. In this case the previous solution applies directly to FooDispatch. Then, you have to convert OFFSET to a set of multi-dimensional indices N..., at compile time. For this, you would also need to know the dimensions of the table, or better its strides, again at compile time. Having deduced arguments N..., FooDispatch can now call FooFun<N...>(...).

To be more precise, the logic of this offset-to-index conversion is exactly as in Matlab's function ind2sub, only it's a compile-time operation. This would need some work, so if you like this approach and need help in this last step, I'd be happy to assist. In this case, I think you'd better post a new question with the sub-problem appropriately formulated.

All the above imply that the number of dimensions is also known at compile time, so eventually a function of the form

Foo* getFoo(int arr[N]);

would be fine as an interface, but

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

does not make much sense; you could only make a run-time check in the latter case, and maybe throw an exception if dimensions do not agree.

Finally, note that using out-of-range indices at run-time would have the same effect as using out-of-range indices in an ordinary C array.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53